module AlgoliaSearch::ClassMethods

these are the class methods added when AlgoliaSearch is included

Public Class Methods

extended(base) click to toggle source
# File lib/algoliasearch-rails.rb, line 302
def self.extended(base)
  class <<base
    alias_method :without_auto_index, :algolia_without_auto_index unless method_defined? :without_auto_index
    alias_method :reindex!, :algolia_reindex! unless method_defined? :reindex!
    alias_method :reindex, :algolia_reindex unless method_defined? :reindex
    alias_method :index_objects, :algolia_index_objects unless method_defined? :index_objects
    alias_method :index!, :algolia_index! unless method_defined? :index!
    alias_method :remove_from_index!, :algolia_remove_from_index! unless method_defined? :remove_from_index!
    alias_method :clear_index!, :algolia_clear_index! unless method_defined? :clear_index!
    alias_method :search, :algolia_search unless method_defined? :search
    alias_method :raw_search, :algolia_raw_search unless method_defined? :raw_search
    alias_method :search_facet, :algolia_search_facet unless method_defined? :search_facet
    alias_method :search_for_facet_values, :algolia_search_for_facet_values unless method_defined? :search_for_facet_values
    alias_method :index_name, :algolia_index_name unless method_defined? :index_name
    alias_method :must_reindex?, :algolia_must_reindex? unless method_defined? :must_reindex?
  end

  base.cattr_accessor :algoliasearch_options, :algoliasearch_settings
end

Public Instance Methods

algolia_clear_index!(synchronous = false) click to toggle source
# File lib/algoliasearch-rails.rb, line 609
def algolia_clear_index!(synchronous = false)
  algolia_configurations.each do |options, settings|
    next if algolia_indexing_disabled?(options) || options[:replica]

    algolia_ensure_init(options, settings)
    index_name = algolia_index_name(options)
    res = AlgoliaSearch.client.clear_objects(index_name)

    if synchronous || options[:synchronous]
      AlgoliaSearch.client.wait_for_task(index_name, res.task_id)
    end
  end
  nil
end
algolia_index!(object, synchronous = false) click to toggle source
# File lib/algoliasearch-rails.rb, line 563
def algolia_index!(object, synchronous = false)
  return if algolia_without_auto_index_scope
  algolia_configurations.each do |options, settings|
    next if algolia_indexing_disabled?(options)

    object_id = algolia_object_id_of(object, options)
    index_name = algolia_index_name(options)
    algolia_ensure_init(options, settings)
    next if options[:replica]

    if algolia_indexable?(object, options)
      raise ArgumentError.new("Cannot index a record with a blank objectID") if object_id.blank?
      resp = AlgoliaSearch.client.save_object(index_name, settings.get_attributes(object).merge({ 'objectID' => algolia_object_id_of(object, options) }))
      if synchronous || options[:synchronous]
        AlgoliaSearch.client.wait_for_task(index_name, resp.task_id)
      end
    elsif algolia_conditional_index?(options) && !object_id.blank?
      # remove non-indexable objects
      resp = AlgoliaSearch.client.delete_object(index_name, object_id)
      if synchronous || options[:synchronous]
        AlgoliaSearch.client.wait_for_task(index_name, resp.task_id)
      end
    end
  end
  nil
end
algolia_index_name(options = nil, index_name = nil) click to toggle source
# File lib/algoliasearch-rails.rb, line 724
def algolia_index_name(options = nil, index_name = nil)
  options ||= algoliasearch_options
  name = index_name || options[:index_name] || model_name.to_s.gsub('::', '_')
  name = "#{name}_#{Rails.env.to_s}" if options[:per_environment]
  name
end
algolia_index_objects(objects, synchronous = false) click to toggle source
# File lib/algoliasearch-rails.rb, line 549
def algolia_index_objects(objects, synchronous = false)
  algolia_configurations.each do |options, settings|
    next if algolia_indexing_disabled?(options)
    algolia_ensure_init(options, settings)
    index_name = algolia_index_name(options)

    next if options[:replica]
    tasks = AlgoliaSearch.client.save_objects(index_name, objects.map { |o| settings.get_attributes(o).merge 'objectID' => algolia_object_id_of(o, options) })
    tasks.each do |task|
      AlgoliaSearch.client.wait_for_task(index_name, task.task_id) if synchronous || options[:synchronous]
    end
  end
end
algolia_must_reindex?(object) click to toggle source
# File lib/algoliasearch-rails.rb, line 731
def algolia_must_reindex?(object)
  # use +algolia_dirty?+ method if implemented
  return object.send(:algolia_dirty?) if (object.respond_to?(:algolia_dirty?))
  # Loop over each index to see if a attribute used in records has changed
  algolia_configurations.each do |options, settings|
    next if algolia_indexing_disabled?(options)
    next if options[:replica]
    return true if algolia_object_id_changed?(object, options)
    settings.get_attribute_names(object).each do |k|
      return true if algolia_attribute_changed?(object, k, true)
    end
    [options[:if], options[:unless]].each do |condition|
      case condition
      when nil
      when String, Symbol
        return true if algolia_attribute_changed?(object, condition, true)
      else
        # if the :if, :unless condition is a anything else,
        # we have no idea whether we should reindex or not
        # let's always reindex then
        return true
      end
    end
  end
  # By default, we don't reindex
  return false
end
algolia_reindex(batch_size = AlgoliaSearch::IndexSettings::DEFAULT_BATCH_SIZE, synchronous = false) click to toggle source

reindex whole database using a extra temporary index + move operation

# File lib/algoliasearch-rails.rb, line 472
def algolia_reindex(batch_size = AlgoliaSearch::IndexSettings::DEFAULT_BATCH_SIZE, synchronous = false)
  return if algolia_without_auto_index_scope
  algolia_configurations.each do |options, settings|
    next if algolia_indexing_disabled?(options)
    next if options[:replica]

    algolia_ensure_init(options, settings)
    index_name = algolia_index_name(options)

    # fetch the master settings
    master_settings = AlgoliaSearch.client.get_settings(index_name).to_hash rescue {} # if master doesn't exist yet
    master_exists = master_settings != {}
    master_settings.merge!(settings.to_hash)

    # remove the replicas of the temporary index
    master_settings.delete :replicas
    master_settings.delete 'replicas'

    # init temporary index
    tmp_index_name = "#{index_name}.tmp"
    tmp_options = options.merge({ :index_name => tmp_index_name })
    tmp_options.delete(:per_environment) # already included in the temporary index_name
    tmp_settings = settings.dup

    if options[:check_settings] == false && master_exists
      task_id = AlgoliaSearch.client.operation_index(
        index_name,
        Algolia::Search::OperationIndexParams.new(operation: Algolia::Search::OperationType::COPY, destination: tmp_index_name, scope: %w[settings synonyms rules])
      ).task_id
      AlgoliaSearch.client.wait_for_task(index_name, task_id)
    end

    algolia_find_in_batches(batch_size) do |group|
      if algolia_conditional_index?(options)
        # select only indexable objects
        group = group.select { |o| algolia_indexable?(o, tmp_options) }
      end
      objects = group.map { |o| tmp_settings.get_attributes(o).merge 'objectID' => algolia_object_id_of(o, tmp_options) }

      AlgoliaSearch.client.save_objects(tmp_index_name, objects)
    end

    task_id = AlgoliaSearch.client.operation_index(
      tmp_index_name,
      Algolia::Search::OperationIndexParams.new(operation: "move", destination: index_name)
    ).task_id
    AlgoliaSearch.client.wait_for_task(index_name, task_id) if synchronous || options[:synchronous]
  end
  nil
end
algolia_reindex!(batch_size = AlgoliaSearch::IndexSettings::DEFAULT_BATCH_SIZE, synchronous = false) click to toggle source
# File lib/algoliasearch-rails.rb, line 440
def algolia_reindex!(batch_size = AlgoliaSearch::IndexSettings::DEFAULT_BATCH_SIZE, synchronous = false)
  return if algolia_without_auto_index_scope
  algolia_configurations.each do |options, settings|
    next if algolia_indexing_disabled?(options)
    algolia_ensure_init(options, settings)
    index_name = algolia_index_name(options)
    next if options[:replica]
    last_task = nil

    algolia_find_in_batches(batch_size) do |group|
      if algolia_conditional_index?(options)
        # delete non-indexable objects
        ids = group.select { |o| !algolia_indexable?(o, options) }.map { |o| algolia_object_id_of(o, options) }
        AlgoliaSearch.client.delete_objects(index_name, ids.select { |id| !id.blank? })
        # select only indexable objects
        group = group.select { |o| algolia_indexable?(o, options) }
      end
      objects = group.map do |o|
        attributes = settings.get_attributes(o)
        unless attributes.class == Hash
          attributes = attributes.to_hash
        end
        attributes.merge 'objectID' => algolia_object_id_of(o, options)
      end
      last_task = AlgoliaSearch.client.save_objects(index_name, objects).last.task_id
    end
    AlgoliaSearch.client.wait_for_task(index_name, last_task) if last_task and (synchronous || options[:synchronous])
  end
  nil
end
algolia_remove_from_index!(object, synchronous = false) click to toggle source
# File lib/algoliasearch-rails.rb, line 590
def algolia_remove_from_index!(object, synchronous = false)
  return if algolia_without_auto_index_scope
  object_id = algolia_object_id_of(object)
  raise ArgumentError.new("Cannot index a record with a blank objectID") if object_id.blank?
  algolia_configurations.each do |options, settings|
    next if algolia_indexing_disabled?(options)
    algolia_ensure_init(options, settings)
    index_name = algolia_index_name(options)

    next if options[:replica]

    resp = AlgoliaSearch.client.delete_object(index_name, object_id)
    if synchronous || options[:synchronous]
      AlgoliaSearch.client.wait_for_task(index_name, resp.task_id)
    end
  end
  nil
end
algolia_search_facet(facet, text, params = {})

deprecated (renaming)

algolia_search_for_facet_values(facet, text, params = {}) click to toggle source
# File lib/algoliasearch-rails.rb, line 700
def algolia_search_for_facet_values(facet, text, params = {})
  index_name = params.delete(:index) ||
               params.delete('index') ||
               params.delete(:replica) ||
               params.delete('replicas')
  index_name ||= algolia_index_name(algoliasearch_options)
  req = Algolia::Search::SearchForFacetValuesRequest.new({facet_query: text, params: params.to_query})

  AlgoliaSearch.client.search_for_facet_values(index_name, facet, req).facet_hits
end
Also aliased as: algolia_search_facet
algolia_set_settings(synchronous = false) click to toggle source
# File lib/algoliasearch-rails.rb, line 523
def algolia_set_settings(synchronous = false)
  algolia_configurations.each do |options, settings|
    if options[:primary_settings] && options[:inherit]
      primary = options[:primary_settings].to_settings.to_hash
      primary.delete :replicas
      primary.delete 'replicas'
      final_settings = primary.merge(settings.to_settings.to_hash)
    else
      final_settings = settings.to_settings.to_hash
    end

    s = final_settings.map do |k, v|
      [settings.setting_name(k), v]
    end.to_h

    synonyms = s.delete("synonyms") || s.delete(:synonyms)
    unless synonyms.nil? || synonyms.empty?
      resp = AlgoliaSearch.client.save_synonyms(index_name,synonyms.map {|s| Algolia::Search::SynonymHit.new({object_id: s.join("-"), synonyms: s, type: "synonym"}) } )
      AlgoliaSearch.client.wait_for_task(index_name, resp.task_id) if synchronous || options[:synchronous]
    end

    resp = AlgoliaSearch.client.set_settings(index_name, Algolia::Search::IndexSettings.new(s))
    AlgoliaSearch.client.wait_for_task(index_name, resp.task_id) if synchronous || options[:synchronous]
  end
end
algolia_without_auto_index() { || ... } click to toggle source
# File lib/algoliasearch-rails.rb, line 423
def algolia_without_auto_index(&block)
  self.algolia_without_auto_index_scope = true
  begin
    yield
  ensure
    self.algolia_without_auto_index_scope = false
  end
end
algolia_without_auto_index_scope() click to toggle source
# File lib/algoliasearch-rails.rb, line 436
def algolia_without_auto_index_scope
  Thread.current["algolia_without_auto_index_scope_for_#{self.model_name}"]
end
algolia_without_auto_index_scope=(value) click to toggle source
# File lib/algoliasearch-rails.rb, line 432
def algolia_without_auto_index_scope=(value)
  Thread.current["algolia_without_auto_index_scope_for_#{self.model_name}"] = value
end
algoliasearch(options = {}, &block) click to toggle source
Calls superclass method
# File lib/algoliasearch-rails.rb, line 322
def algoliasearch(options = {}, &block)
  self.algoliasearch_settings = IndexSettings.new(options, &block)
  self.algoliasearch_options = { :type => algolia_full_const_get(model_name.to_s), :per_page => algoliasearch_settings.get_setting(:hitsPerPage) || 10, :page => 1 }.merge(options)

  attr_accessor :highlight_result, :snippet_result

  if options[:synchronous] == true
    if defined?(::Sequel) && defined?(::Sequel::Model) && self < Sequel::Model
      class_eval do
        copy_after_validation = instance_method(:after_validation)
        define_method(:after_validation) do |*args|
          super(*args)
          copy_after_validation.bind(self).call
          algolia_mark_synchronous
        end
      end
    else
      after_validation :algolia_mark_synchronous if respond_to?(:after_validation)
    end
  end
  if options[:enqueue]
    raise ArgumentError.new("Cannot use a enqueue if the `synchronous` option if set") if options[:synchronous]
    proc = if options[:enqueue] == true
      Proc.new do |record, remove|
        AlgoliaJob.perform_later(record, remove ? 'algolia_remove_from_index!' : 'algolia_index!')
      end
    elsif options[:enqueue].respond_to?(:call)
      options[:enqueue]
    elsif options[:enqueue].is_a?(Symbol)
      Proc.new { |record, remove| self.send(options[:enqueue], record, remove) }
    else
      raise ArgumentError.new("Invalid `enqueue` option: #{options[:enqueue]}")
    end
    algoliasearch_options[:enqueue] = Proc.new do |record, remove|
      proc.call(record, remove) unless algolia_without_auto_index_scope
    end
  end
  unless options[:auto_index] == false
    if defined?(::Sequel) && defined?(::Sequel::Model) && self < Sequel::Model
      class_eval do
        copy_after_validation = instance_method(:after_validation)
        copy_before_save = instance_method(:before_save)

        define_method(:after_validation) do |*args|
          super(*args)
          copy_after_validation.bind(self).call
          algolia_mark_must_reindex
        end

        define_method(:before_save) do |*args|
          copy_before_save.bind(self).call
          algolia_mark_for_auto_indexing
          super(*args)
        end

        sequel_version = Gem::Version.new(Sequel.version)
        if sequel_version >= Gem::Version.new('4.0.0') && sequel_version < Gem::Version.new('5.0.0')
          copy_after_commit = instance_method(:after_commit)
          define_method(:after_commit) do |*args|
            super(*args)
            copy_after_commit.bind(self).call
            algolia_perform_index_tasks
          end
        else
          copy_after_save = instance_method(:after_save)
          define_method(:after_save) do |*args|
            super(*args)
            copy_after_save.bind(self).call
            self.db.after_commit do
              algolia_perform_index_tasks
            end
          end
        end
      end
    else
      after_validation :algolia_mark_must_reindex if respond_to?(:after_validation)
      before_save :algolia_mark_for_auto_indexing if respond_to?(:before_save)
      if respond_to?(:after_commit)
        after_commit :algolia_perform_index_tasks
      elsif respond_to?(:after_save)
        after_save :algolia_perform_index_tasks
      end
    end
  end
  unless options[:auto_remove] == false
    if defined?(::Sequel) && defined?(::Sequel::Model) && self < Sequel::Model
      class_eval do
        copy_after_destroy = instance_method(:after_destroy)

        define_method(:after_destroy) do |*args|
          copy_after_destroy.bind(self).call
          algolia_enqueue_remove_from_index!(algolia_synchronous?)
          super(*args)
        end
      end
    else
      after_destroy { |searchable| searchable.algolia_enqueue_remove_from_index!(algolia_synchronous?) } if respond_to?(:after_destroy)
    end
  end
end
ensure_algolia_index(name = nil) click to toggle source
# File lib/algoliasearch-rails.rb, line 714
def ensure_algolia_index(name = nil)
  if name
    algolia_configurations.each do |o, s|
      return algolia_ensure_init(o, s) if o[:index_name].to_s == name.to_s
    end
    raise ArgumentError.new("Invalid index/replica name: #{name}")
  end
  algolia_ensure_init
end

Protected Instance Methods

algolia_ensure_init(options = nil, settings = nil, index_settings_hash = nil) click to toggle source
# File lib/algoliasearch-rails.rb, line 761
def algolia_ensure_init(options = nil, settings = nil, index_settings_hash = nil)
  raise ArgumentError.new('No `algoliasearch` block found in your model.') if algoliasearch_settings.nil?

  @algolia_indexes_init ||= {}

  options ||= algoliasearch_options
  settings ||= algoliasearch_settings

  return if @algolia_indexes_init[settings]

  index_name = algolia_index_name(options)


  index_settings_hash ||= settings.to_settings.to_hash
  index_settings_hash = options[:primary_settings].to_settings.to_hash.merge(index_settings_hash) if options[:inherit]
  replicas = index_settings_hash.delete(:replicas) || index_settings_hash.delete('replicas')
  index_settings_hash[:replicas] = replicas unless replicas.nil? || options[:inherit]

  options[:check_settings] = true if options[:check_settings].nil?

  current_settings = if options[:check_settings] && !algolia_indexing_disabled?(options)
                       AlgoliaSearch.client.get_settings(index_name, {:getVersion => 1}).to_hash rescue nil # if the index doesn't exist
                     end

  if !algolia_indexing_disabled?(options) && options[:check_settings] && algoliasearch_settings_changed?(current_settings, index_settings_hash)
    s = index_settings_hash.map do |k, v|
      [settings.setting_name(k), v]
    end.to_h

    synonyms = s.delete("synonyms") || s.delete(:synonyms)
    unless synonyms.nil? || synonyms.empty?
      resp = AlgoliaSearch.client.save_synonyms(index_name,synonyms.map {|s| Algolia::Search::SynonymHit.new({object_id: s.join("-"), synonyms: s, type: "synonym"}) } )
      AlgoliaSearch.client.wait_for_task(index_name, resp.task_id) if options[:synchronous]
    end

    resp = AlgoliaSearch.client.set_settings(index_name, Algolia::Search::IndexSettings.new(s))
    AlgoliaSearch.client.wait_for_task(index_name, resp.task_id) if options[:synchronous]
  end

  return
end

Private Instance Methods

algolia_attribute_changed?(object, attr_name, default) click to toggle source
# File lib/algoliasearch-rails.rb, line 930
def algolia_attribute_changed?(object, attr_name, default)
  # if one of two method is implemented, we return its result
  # true/false means whether it has changed or not
  # +#{attr_name}_changed?+ always defined for automatic attributes but deprecated after Rails 5.2
  # +will_save_change_to_#{attr_name}?+ should be use instead for Rails 5.2+, also defined for automatic attributes.
  # If none of the method are defined, it's a dynamic attribute

  method_name = "#{attr_name}_changed?"
  if object.respond_to?(method_name)
    # If +#{attr_name}_changed?+ respond we want to see if the method is user defined or if it's automatically
    # defined by Rails.
    # If it's user-defined, we call it.
    # If it's automatic we check ActiveRecord version to see if this method is deprecated
    # and try to call +will_save_change_to_#{attr_name}?+ instead.
    # See: https://github.com/algolia/algoliasearch-rails/pull/338
    # This feature is not compatible with Ruby 1.8
    # In this case, we always call #{attr_name}_changed?
    if Object.const_defined?(:RUBY_VERSION) && RUBY_VERSION.to_f < 1.9
      return object.send(method_name)
    end
    unless automatic_changed_method?(object, method_name) && automatic_changed_method_deprecated?
      return object.send(method_name)
    end
  end

  if object.respond_to?("will_save_change_to_#{attr_name}?")
    return object.send("will_save_change_to_#{attr_name}?")
  end

  # We don't know if the attribute has changed, so return the default passed
  default
end
algolia_conditional_index?(options = nil) click to toggle source
# File lib/algoliasearch-rails.rb, line 865
def algolia_conditional_index?(options = nil)
  options ||= algoliasearch_options
  options[:if].present? || options[:unless].present?
end
algolia_configurations() click to toggle source
# File lib/algoliasearch-rails.rb, line 805
def algolia_configurations
  raise ArgumentError.new('No `algoliasearch` block found in your model.') if algoliasearch_settings.nil?
  if @configurations.nil?
    @configurations = {}
    @configurations[algoliasearch_options] = algoliasearch_settings
    algoliasearch_settings.additional_indexes.each do |k,v|
      @configurations[k] = v

      if v.additional_indexes.any?
        v.additional_indexes.each do |options, index|
          @configurations[options] = index
        end
      end
    end
  end
  @configurations
end
algolia_constraint_passes?(object, constraint) click to toggle source
# File lib/algoliasearch-rails.rb, line 877
def algolia_constraint_passes?(object, constraint)
  case constraint
  when Symbol
    object.send(constraint)
  when String
    object.send(constraint.to_sym)
  when Enumerable
    # All constraints must pass
    constraint.all? { |inner_constraint| algolia_constraint_passes?(object, inner_constraint) }
  else
    if constraint.respond_to?(:call) # Proc
      constraint.call(object)
    else
      raise ArgumentError, "Unknown constraint type: #{constraint} (#{constraint.class})"
    end
  end
end
algolia_find_in_batches(batch_size) { |items| ... } click to toggle source
# File lib/algoliasearch-rails.rb, line 911
def algolia_find_in_batches(batch_size, &block)
  if (defined?(::ActiveRecord) && ancestors.include?(::ActiveRecord::Base)) || respond_to?(:find_in_batches)
    find_in_batches(:batch_size => batch_size, &block)
  elsif defined?(::Sequel) && defined?(::Sequel::Model) && self < Sequel::Model
    dataset.extension(:pagination).each_page(batch_size, &block)
  else
    # don't worry, mongoid has its own underlying cursor/streaming mechanism
    items = []
    all.each do |item|
      items << item
      if items.length % batch_size == 0
        yield items
        items = []
      end
    end
    yield items unless items.empty?
  end
end
algolia_full_const_get(name) click to toggle source
# File lib/algoliasearch-rails.rb, line 853
def algolia_full_const_get(name)
  list = name.split('::')
  list.shift if list.first.blank?
  obj = Object.const_defined?(:RUBY_VERSION) && RUBY_VERSION.to_f < 1.9 ? Object : self
  list.each do |x|
    # This is required because const_get tries to look for constants in the
    # ancestor chain, but we only want constants that are HERE
    obj = obj.const_defined?(x) ? obj.const_get(x) : obj.const_missing(x)
  end
  obj
end
algolia_indexable?(object, options = nil) click to toggle source
# File lib/algoliasearch-rails.rb, line 870
def algolia_indexable?(object, options = nil)
  options ||= algoliasearch_options
  if_passes = options[:if].blank? || algolia_constraint_passes?(object, options[:if])
  unless_passes = options[:unless].blank? || !algolia_constraint_passes?(object, options[:unless])
  if_passes && unless_passes
end
algolia_indexing_disabled?(options = nil) click to toggle source
# File lib/algoliasearch-rails.rb, line 895
def algolia_indexing_disabled?(options = nil)
  options ||= algoliasearch_options
  constraint = options[:disable_indexing] || options['disable_indexing']
  case constraint
  when nil
    return false
  when true, false
    return constraint
  when String, Symbol
    return send(constraint)
  else
    return constraint.call if constraint.respond_to?(:call) # Proc
  end
  raise ArgumentError, "Unknown constraint type: #{constraint} (#{constraint.class})"
end
algolia_object_id_changed?(o, options = nil) click to toggle source
# File lib/algoliasearch-rails.rb, line 832
def algolia_object_id_changed?(o, options = nil)
  changed = algolia_attribute_changed?(o, algolia_object_id_method(options), false)
  changed.nil? ? false : changed
end
algolia_object_id_method(options = nil) click to toggle source
# File lib/algoliasearch-rails.rb, line 823
def algolia_object_id_method(options = nil)
  options ||= algoliasearch_options
  options[:id] || options[:object_id] || :id
end
algolia_object_id_of(o, options = nil) click to toggle source
# File lib/algoliasearch-rails.rb, line 828
def algolia_object_id_of(o, options = nil)
  o.send(algolia_object_id_method(options)).to_s
end
algoliasearch_settings_changed?(prev, current) click to toggle source
# File lib/algoliasearch-rails.rb, line 837
def algoliasearch_settings_changed?(prev, current)
  return true if prev.nil?
  current.each do |k, v|
    prev_v = prev[k.to_sym] || prev[k.to_s]
    if v.is_a?(Array) and prev_v.is_a?(Array)
      # compare array of strings, avoiding symbols VS strings comparison
      return true if v.map { |x| x.to_s } != prev_v.map { |x| x.to_s }
    elsif v.blank? # blank? check is needed to compare [] and null
      return true unless prev_v.blank?
    else
      return true if prev_v != v
    end
  end
  false
end
automatic_changed_method?(object, method_name) click to toggle source
# File lib/algoliasearch-rails.rb, line 963
def automatic_changed_method?(object, method_name)
  raise ArgumentError.new("Method #{method_name} doesn't exist on #{object.class.name}") unless object.respond_to?(method_name)
  file = object.method(method_name).source_location[0]
  file.end_with?("active_model/attribute_methods.rb")
end
automatic_changed_method_deprecated?() click to toggle source
# File lib/algoliasearch-rails.rb, line 969
def automatic_changed_method_deprecated?
  (defined?(::ActiveRecord) && ActiveRecord::VERSION::MAJOR >= 5 && ActiveRecord::VERSION::MINOR >= 1) ||
      (defined?(::ActiveRecord) && ActiveRecord::VERSION::MAJOR > 5)
end