module AlgoliaSearch::ClassMethods
these are the class methods added when AlgoliaSearch
is included
Public Class Methods
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
Source
# File lib/algoliasearch-rails.rb, line 612 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
Source
# File lib/algoliasearch-rails.rb, line 566 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
Source
# File lib/algoliasearch-rails.rb, line 727 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
Source
# File lib/algoliasearch-rails.rb, line 552 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
Source
# File lib/algoliasearch-rails.rb, line 734 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
Source
# File lib/algoliasearch-rails.rb, line 628 def algolia_raw_search(q, params = {}) index_name_base = params.delete(:index) || params.delete('index') || params.delete(:replica) || params.delete('replica') opts = algoliasearch_options unless index_name_base.nil? algolia_configurations.each do |o, s| if o[:index_name].to_s == index_name_base.to_s opts = o ensure_algolia_index(index_name_base) end end end index_name = algolia_index_name(opts, index_name_base) AlgoliaSearch.client.search_single_index(index_name,Hash[params.to_h.map { |k,v| [k.to_s, v.to_s] }].merge({query: q})).to_hash end
Source
# File lib/algoliasearch-rails.rb, line 473 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) else algolia_ensure_init(tmp_options, tmp_settings, master_settings) 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
reindex whole database using a extra temporary index + move operation
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 save_tasks = AlgoliaSearch.client.save_objects(index_name, objects) last_task = save_tasks.present? ? save_tasks.last.task_id : last_task end AlgoliaSearch.client.wait_for_task(index_name, last_task) if last_task and (synchronous || options[:synchronous]) end nil end
Source
# File lib/algoliasearch-rails.rb, line 593 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
Source
# File lib/algoliasearch-rails.rb, line 670 def algolia_search(q, params = {}) if AlgoliaSearch.configuration[:pagination_backend] # kaminari, will_paginate, and pagy start pagination at 1, Algolia starts at 0 params[:page] = (params.delete('page') || params.delete(:page)).to_i params[:page] -= 1 if params[:page].to_i > 0 end json = algolia_raw_search(q, params) hit_ids = json[:hits].map { |hit| hit[:objectID] } if defined?(::Mongoid::Document) && self.include?(::Mongoid::Document) condition_key = algolia_object_id_method.in else condition_key = algolia_object_id_method end results_by_id = algoliasearch_options[:type].where(condition_key => hit_ids).index_by do |hit| algolia_object_id_of(hit) end results = json[:hits].map do |hit| o = results_by_id[hit[:objectID].to_s] if o o.highlight_result = hit[:_highlightResult] o.snippet_result = hit[:_snippetResult] o end end.compact # Algolia has a default limit of 1000 retrievable hits total_hits = json[:nbHits].to_i < json[:nbPages].to_i * json[:hitsPerPage].to_i ? json[:nbHits].to_i: json[:nbPages].to_i * json[:hitsPerPage].to_i res = AlgoliaSearch::Pagination.create(results, total_hits, algoliasearch_options.merge({ :page => json[:page].to_i + 1, :per_page => json[:hitsPerPage] })) res.extend(AdditionalMethods) res.send(:algolia_init_raw_answer, json) res end
deprecated (renaming)
Alias for: algolia_search_for_facet_values
Source
# File lib/algoliasearch-rails.rb, line 703 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
Source
# File lib/algoliasearch-rails.rb, line 526 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({algolia_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
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
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
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
Source
# 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
Calls superclass method
Source
# File lib/algoliasearch-rails.rb, line 717 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
Source
# File lib/algoliasearch-rails.rb, line 764 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 index_name = algolia_index_name(options) return if @algolia_indexes_init[index_name] @algolia_indexes_init[index_name] = settings 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({algolia_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
Source
# File lib/algoliasearch-rails.rb, line 934 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
Source
# File lib/algoliasearch-rails.rb, line 869 def algolia_conditional_index?(options = nil) options ||= algoliasearch_options options[:if].present? || options[:unless].present? end
Source
# File lib/algoliasearch-rails.rb, line 809 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
Source
# File lib/algoliasearch-rails.rb, line 881 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
Source
# File lib/algoliasearch-rails.rb, line 915 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
Source
# File lib/algoliasearch-rails.rb, line 857 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
Source
# File lib/algoliasearch-rails.rb, line 874 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
Source
# File lib/algoliasearch-rails.rb, line 899 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
Source
# File lib/algoliasearch-rails.rb, line 836 def algolia_object_id_changed?(o, options = nil) changed = algolia_attribute_changed?(o, algolia_object_id_method(options), false) changed.nil? ? false : changed end
Source
# File lib/algoliasearch-rails.rb, line 827 def algolia_object_id_method(options = nil) options ||= algoliasearch_options options[:id] || options[:object_id] || :id end
Source
# File lib/algoliasearch-rails.rb, line 832 def algolia_object_id_of(o, options = nil) o.send(algolia_object_id_method(options)).to_s end
Source
# File lib/algoliasearch-rails.rb, line 841 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
Source
# File lib/algoliasearch-rails.rb, line 967 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
Source
# File lib/algoliasearch-rails.rb, line 973 def automatic_changed_method_deprecated? (defined?(::ActiveRecord) && ActiveRecord::VERSION::MAJOR >= 5 && ActiveRecord::VERSION::MINOR >= 1) || (defined?(::ActiveRecord) && ActiveRecord::VERSION::MAJOR > 5) end