Create index with new searchkick options in rails console

I am looking for a way to seamlessly update my Elasticsearch index.

I am moving from:

class Product < ApplicationRecord
  searchkick
end

To:

class Product < ApplicationRecord
  searchkick text_middle: [:name, :item_volume]
end

My current idea is to

  1. open a rails console on target environment
  2. update searchkick options of my Product class as above
  3. create new index with Product.reindex(async: true)
  4. promote the new index once my app is deployed with Product.search_index.promote(index_name).

Unfortunately, when doing that, searchkick won't let me change the options and raises:

RuntimeError (Only call searchkick once per model)

Any idea?


Traceback (most recent call last):
       10: from bin/rails:9:in `<main>'
        9: from bin/rails:9:in `require'
        8: from /app/vendor/bundle/ruby/2.5.0/gems/railties-4.2.11/lib/rails/commands.rb:17:in `<top (required)>'
        7: from /app/vendor/bundle/ruby/2.5.0/gems/railties-4.2.11/lib/rails/commands/commands_tasks.rb:39:in `run_command!'
        6: from /app/vendor/bundle/ruby/2.5.0/gems/railties-4.2.11/lib/rails/commands/commands_tasks.rb:68:in `console'
        5: from /app/vendor/bundle/ruby/2.5.0/gems/railties-4.2.11/lib/rails/commands/console.rb:9:in `start'
        4: from /app/vendor/bundle/ruby/2.5.0/gems/railties-4.2.11/lib/rails/commands/console.rb:110:in `start'
        3: from (irb):1
        2: from (irb):8:in `<class:Product>'
        1: from /app/vendor/bundle/ruby/2.5.0/gems/searchkick-2.5.0/lib/searchkick/model.rb:11:in `searchkick'

Solution 1:

I would suggest doing this in a worker and you can use perhaps a Config model variable or Redis cache to set up your model to be ready to use the new index once it's promoted.

class UpdateProductIndexWorker
  include Sidekiq::Worker
  sidekiq_options retry: 0

  def peform
    #patch your product model for this job
    class Product
      searchkick text_middle: [:name, :item_volume]
    end
    # track status of reindex
    Searchkick.redis = Redis.new 
    new_index = Product.reindex(async: true)
    index_name = new_index['index_name']  
    loop do
      break if Searchkick.reindex_status(index_name)[:completed]
      sleep 60 # or however long you want to interval check
    end
    # You'll need to tell your app somehow to use new index
    # You could use perhaps a Config model which could be something
    # you could store in the database for exmaple:
    # Config.create(name: 'use_new_products_index', state: true)
    # or use redis assuming it's your Rails cache
    Rails.cache.write('use_new_product_index', true, expires_in: 1.year)  
    # now you can just use the new index
    Product.searchkick_index.promote(index_name)
  end
end

Update your model to use new index when it's ready.

class Product < ApplicationRecord

  def self.use_new_index?
    # Config.find_by(name: 'use_new_products_index').try(:state)
    # or use Rails/redis cachea
    Rails.cache.read('use_new_product_index')
  end
  # use the config data to make the model backward compatable until
  # the new index is ready.  Later you'll remove this in a cleanup deploy.
  if Product.use_new_index?
    searchkick text_middle: [:name, :item_volume]
  else
    searchkick
  end
end