ruby 39 lines · 9 steps

Keeping a search index fresh in Rails

An Article model uses after_commit callbacks and a debounced job to keep an external search index in sync only when meaningful fields change.

Explained by highlit
1class Article < ApplicationRecord
2 belongs_to :author
3 has_many :taggings, dependent: :destroy
4 has_many :tags, through: :taggings
5 
6 INDEX_DEBOUNCE = 5.minutes
7 
8 after_commit :schedule_search_reindex, on: %i[create update]
9 after_commit :remove_from_search_index, on: :destroy
10 
11 scope :pending_reindex, -> { where("indexed_at IS NULL OR indexed_at < updated_at") }
12 
13 def search_index_stale?
14 indexed_at.nil? || indexed_at < updated_at
15 end
16 
17 def mark_indexed!
18 update_column(:indexed_at, Time.current)
19 end
20 
21 private
22 
23 def schedule_search_reindex
24 return unless search_index_stale?
25 return unless saved_change_to_indexable_attributes?
26 
27 ReindexArticleJob
28 .set(wait: INDEX_DEBOUNCE)
29 .perform_later(id)
30 end
31 
32 def remove_from_search_index
33 RemoveFromSearchIndexJob.perform_later(self.class.name, id)
34 end
35 
36 def saved_change_to_indexable_attributes?
37 (saved_changes.keys & %w[title body summary published_at author_id]).any?
38 end
39end
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1after_commit runs jobs only after the transaction succeeds, so you never enqueue work for a rollback.
  2. 2Guarding reindexing behind a whitelist of changed columns avoids wasteful work on irrelevant updates.
  3. 3Debouncing job enqueues with a wait window collapses rapid edits into a single reindex.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Keeping a search index fresh in Rails — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code