lib/meilisearch-rails.rb in meilisearch-rails-0.2.3 vs lib/meilisearch-rails.rb in meilisearch-rails-0.3.0

- old
+ new

@@ -1,9 +1,10 @@ require 'meilisearch' require 'meilisearch/version' require 'meilisearch/utilities' +require 'meilisearch/errors' if defined? Rails begin require 'meilisearch/railtie' rescue LoadError @@ -17,15 +18,10 @@ end require 'logger' module MeiliSearch - - class NotConfigured < StandardError; end - class BadConfiguration < StandardError; end - class NoBlockGiven < StandardError; end - autoload :Configuration, 'meilisearch/configuration' extend Configuration autoload :Pagination, 'meilisearch/pagination' @@ -40,31 +36,30 @@ klass.class_eval do extend ClassMethods include InstanceMethods end end - end class IndexSettings DEFAULT_BATCH_SIZE = 1000 - DEFAULT_PRIMARY_KEY = 'id' + DEFAULT_PRIMARY_KEY = 'id'.freeze # MeiliSearch settings - OPTIONS = [ - :searchableAttributes, - :filterableAttributes, - :displayedAttributes, - :distinctAttribute, - :synonyms, - :stopWords, - :rankingRules, - :attributesToHighlight, - :attributesToCrop, - :cropLength, - ] + OPTIONS = %i[ + searchableAttributes + filterableAttributes + displayedAttributes + distinctAttribute + synonyms + stopWords + rankingRules + attributesToHighlight + attributesToCrop + cropLength + ].freeze OPTIONS.each do |option| define_method option do |value| instance_variable_set("@#{option}", value) end @@ -82,44 +77,46 @@ @serializer = serializer # instance_variable_set("@serializer", serializer) end def attribute(*names, &block) - raise ArgumentError.new('Cannot pass multiple attribute names if block given') if block_given? and names.length > 1 + raise ArgumentError, 'Cannot pass multiple attribute names if block given' if block_given? && (names.length > 1) + @attributes ||= {} names.flatten.each do |name| - @attributes[name.to_s] = block_given? ? Proc.new { |d| d.instance_eval(&block) } : Proc.new { |d| d.send(name) } + @attributes[name.to_s] = block_given? ? proc { |d| d.instance_eval(&block) } : proc { |d| d.send(name) } end end - alias :attributes :attribute + alias attributes attribute def add_attribute(*names, &block) - raise ArgumentError.new('Cannot pass multiple attribute names if block given') if block_given? and names.length > 1 + raise ArgumentError, 'Cannot pass multiple attribute names if block given' if block_given? && (names.length > 1) + @additional_attributes ||= {} names.each do |name| - @additional_attributes[name.to_s] = block_given? ? Proc.new { |d| d.instance_eval(&block) } : Proc.new { |d| d.send(name) } + @additional_attributes[name.to_s] = block_given? ? proc { |d| d.instance_eval(&block) } : proc { |d| d.send(name) } end end - alias :add_attributes :add_attribute + alias add_attributes add_attribute - def is_mongoid?(document) + def mongoid?(document) defined?(::Mongoid::Document) && document.class.include?(::Mongoid::Document) end - def is_sequel?(document) + def sequel?(document) defined?(::Sequel) && document.class < ::Sequel::Model end - def is_active_record?(document) - !is_mongoid?(document) && !is_sequel?(document) + def active_record?(document) + !mongoid?(document) && !sequel?(document) end def get_default_attributes(document) - if is_mongoid?(document) + if mongoid?(document) # work-around mongoid 2.4's unscoped method, not accepting a block document.attributes - elsif is_sequel?(document) + elsif sequel?(document) document.to_hash else document.class.unscoped do document.attributes end @@ -130,35 +127,31 @@ get_attributes(document).keys end def attributes_to_hash(attributes, document) if attributes - Hash[attributes.map { |name, value| [name.to_s, value.call(document) ] }] + attributes.map { |name, value| [name.to_s, value.call(document)] }.to_h else {} end end def get_attributes(document) # If a serializer is set, we ignore attributes # everything should be done via the serializer - if not @serializer.nil? + if !@serializer.nil? attributes = @serializer.new(document).attributes - else - if @attributes.nil? || @attributes.length == 0 + elsif @attributes.blank? + attributes = get_default_attributes(document) # no `attribute ...` have been configured, use the default attributes of the model - attributes = get_default_attributes(document) - else + elsif active_record?(document) # at least 1 `attribute ...` has been configured, therefore use ONLY the one configured - if is_active_record?(document) - document.class.unscoped do - attributes = attributes_to_hash(@attributes, document) - end - else + document.class.unscoped do attributes = attributes_to_hash(@attributes, document) end - end + else + attributes = attributes_to_hash(@attributes, document) end attributes.merge!(attributes_to_hash(@additional_attributes, document)) if @additional_attributes if @options[:sanitize] @@ -169,40 +162,38 @@ ::Rails::Html::FullSanitizer.new end attributes = sanitize_attributes(attributes, sanitizer) end - if @options[:force_utf8_encoding] - attributes = encode_attributes(attributes) - end + attributes = encode_attributes(attributes) if @options[:force_utf8_encoding] attributes end - def sanitize_attributes(v, sanitizer) - case v + def sanitize_attributes(value, sanitizer) + case value when String - sanitizer.sanitize(v) + sanitizer.sanitize(value) when Hash - v.each { |key, value| v[key] = sanitize_attributes(value, sanitizer) } + value.each { |key, val| value[key] = sanitize_attributes(val, sanitizer) } when Array - v.map { |x| sanitize_attributes(x, sanitizer) } + value.map { |item| sanitize_attributes(item, sanitizer) } else - v + value end end - def encode_attributes(v) - case v + def encode_attributes(value) + case value when String - v.force_encoding('utf-8') + value.force_encoding('utf-8') when Hash - v.each { |key, value| v[key] = encode_attributes(value) } + value.each { |key, val| value[key] = encode_attributes(val) } when Array - v.map { |x| encode_attributes(x) } + value.map { |x| encode_attributes(x) } else - v + value end end def get_setting(name) instance_variable_get("@#{name}") @@ -210,18 +201,21 @@ def to_settings settings = {} OPTIONS.each do |k| v = get_setting(k) - settings[k] = v if !v.nil? + settings[k] = v unless v.nil? end settings end def add_index(index_uid, options = {}, &block) - raise ArgumentError.new('No block given') if !block_given? - raise ArgumentError.new('Options auto_index and auto_remove cannot be set on nested indexes') if options[:auto_index] || options[:auto_remove] + raise ArgumentError, 'No block given' unless block_given? + if options[:auto_index] || options[:auto_remove] + raise ArgumentError, 'Options auto_index and auto_remove cannot be set on nested indexes' + end + @additional_indexes ||= {} options[:index_uid] = index_uid @additional_indexes[options] = IndexSettings.new(options, &block) end @@ -240,74 +234,71 @@ # this class wraps an MeiliSearch::Index document ensuring all raised exceptions # are correctly logged or thrown depending on the `raise_on_failure` option class SafeIndex def initialize(index_uid, raise_on_failure, options) client = MeiliSearch.client - primary_key = options[:primary_key] || MeiliSearch::IndexSettings::DEFAULT_PRIMARY_KEY + primary_key = options[:primary_key] || MeiliSearch::IndexSettings::DEFAULT_PRIMARY_KEY @index = client.get_or_create_index(index_uid, { primaryKey: primary_key }) @raise_on_failure = raise_on_failure.nil? || raise_on_failure end ::MeiliSearch::Index.instance_methods(false).each do |m| - define_method(m) do |*args, &block| - if (m == :update_settings) - args[0].delete(:attributesToHighlight) if args[0][:attributesToHighlight] - args[0].delete(:attributesToCrop) if args[0][:attributesToCrop] - args[0].delete(:cropLength) if args[0][:cropLength] - end - SafeIndex.log_or_throw(m, @raise_on_failure) do - @index.send(m, *args, &block) - end + define_method(m) do |*args, &block| + if m == :update_settings + args[0].delete(:attributesToHighlight) if args[0][:attributesToHighlight] + args[0].delete(:attributesToCrop) if args[0][:attributesToCrop] + args[0].delete(:cropLength) if args[0][:cropLength] end + SafeIndex.log_or_throw(m, @raise_on_failure) do + @index.send(m, *args, &block) + end + end end # special handling of wait_for_pending_update to handle null task_id def wait_for_pending_update(update_id) return if update_id.nil? && !@raise_on_failure # ok + SafeIndex.log_or_throw(:wait_for_pending_update, @raise_on_failure) do @index.wait_for_pending_update(update_id) end end # special handling of settings to avoid raising errors on 404 def settings(*args) SafeIndex.log_or_throw(:settings, @raise_on_failure) do - begin - @index.settings(*args) - rescue ::MeiliSearch::ApiError => e - return {} if e.code == 404 # not fatal - raise e - end + @index.settings(*args) + rescue ::MeiliSearch::ApiError => e + return {} if e.code == 404 # not fatal + + raise e end end - private def self.log_or_throw(method, raise_on_failure, &block) - begin - yield - rescue ::MeiliSearch::ApiError => e - raise e if raise_on_failure - # log the error - (Rails.logger || Logger.new(STDOUT)).error("[meilisearch-rails] #{e.message}") - # return something - case method.to_s - when 'search' - # some attributes are required - { 'hits' => [], 'hitsPerPage' => 0, 'page' => 0, 'facetsDistribution' => {}, 'error' => e } - else - # empty answer - { 'error' => e } - end + yield + rescue ::MeiliSearch::ApiError => e + raise e if raise_on_failure + + # log the error + (Rails.logger || Logger.new($stdout)).error("[meilisearch-rails] #{e.message}") + # return something + case method.to_s + when 'search' + # some attributes are required + { 'hits' => [], 'hitsPerPage' => 0, 'page' => 0, 'facetsDistribution' => {}, 'error' => e } + else + # empty answer + { 'error' => e } end end end # these are the class methods added when MeiliSearch is included module ClassMethods - def self.extended(base) - class <<base + class << base alias_method :without_auto_index, :ms_without_auto_index unless method_defined? :without_auto_index alias_method :reindex!, :ms_reindex! unless method_defined? :reindex! alias_method :index_documents, :ms_index_documents unless method_defined? :index_documents alias_method :index!, :ms_index! unless method_defined? :index! alias_method :remove_from_index!, :ms_remove_from_index! unless method_defined? :remove_from_index! @@ -322,11 +313,14 @@ base.cattr_accessor :meilisearch_options, :meilisearch_settings end def meilisearch(options = {}, &block) self.meilisearch_settings = IndexSettings.new(options, &block) - self.meilisearch_options = { type: ms_full_const_get(model_name.to_s), per_page: meilisearch_settings.get_setting(:hitsPerPage) || 20, page: 1 }.merge(options) + self.meilisearch_options = { + type: ms_full_const_get(model_name.to_s), + per_page: meilisearch_settings.get_setting(:hitsPerPage) || 20, page: 1 + }.merge(options) attr_accessor :formatted if options[:synchronous] == true if defined?(::Sequel) && self < Sequel::Model @@ -336,28 +330,29 @@ super(*args) copy_after_validation.bind(self).call ms_mark_synchronous end end - else - after_validation :ms_mark_synchronous if respond_to?(:after_validation) + elsif respond_to?(:after_validation) + after_validation :ms_mark_synchronous end end if options[:enqueue] - raise ArgumentError.new("Cannot use a enqueue if the `synchronous` option if set") if options[:synchronous] + raise ArgumentError, 'Cannot use a enqueue if the `synchronous` option if set' if options[:synchronous] + proc = if options[:enqueue] == true - Proc.new do |record, remove| - MSJob.perform_later(record, remove ? 'ms_remove_from_index!' : 'ms_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 - meilisearch_options[:enqueue] = Proc.new do |record, remove| + proc do |record, remove| + MSJob.perform_later(record, remove ? 'ms_remove_from_index!' : 'ms_index!') + end + elsif options[:enqueue].respond_to?(:call) + options[:enqueue] + elsif options[:enqueue].is_a?(Symbol) + proc { |record, remove| send(options[:enqueue], record, remove) } + else + raise ArgumentError, "Invalid `enqueue` option: #{options[:enqueue]}" + end + meilisearch_options[:enqueue] = proc do |record, remove| proc.call(record, remove) unless ms_without_auto_index_scope end end unless options[:auto_index] == false if defined?(::Sequel) && self < Sequel::Model @@ -388,11 +383,11 @@ 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 + db.after_commit do ms_perform_index_tasks end end end end @@ -415,12 +410,12 @@ copy_after_destroy.bind(self).call ms_enqueue_remove_from_index!(ms_synchronous?) super(*args) end end - else - after_destroy { |searchable| searchable.ms_enqueue_remove_from_index!(ms_synchronous?) } if respond_to?(:after_destroy) + elsif respond_to?(:after_destroy) + after_destroy { |searchable| searchable.ms_enqueue_remove_from_index!(ms_synchronous?) } end end end def ms_without_auto_index(&block) @@ -431,42 +426,42 @@ self.ms_without_auto_index_scope = false end end def ms_without_auto_index_scope=(value) - Thread.current["ms_without_auto_index_scope_for_#{self.model_name}"] = value + Thread.current["ms_without_auto_index_scope_for_#{model_name}"] = value end def ms_without_auto_index_scope - Thread.current["ms_without_auto_index_scope_for_#{self.model_name}"] + Thread.current["ms_without_auto_index_scope_for_#{model_name}"] end def ms_reindex!(batch_size = MeiliSearch::IndexSettings::DEFAULT_BATCH_SIZE, synchronous = false) return if ms_without_auto_index_scope + ms_configurations.each do |options, settings| next if ms_indexing_disabled?(options) + index = ms_ensure_init(options, settings) last_update = nil ms_find_in_batches(batch_size) do |group| if ms_conditional_index?(options) # delete non-indexable documents ids = group.select { |d| !ms_indexable?(d, options) }.map { |d| ms_primary_key_of(d, options) } - index.delete_documents(ids.select { |id| !id.blank? }) + index.delete_documents(ids.select(&:present?)) # select only indexable documents group = group.select { |d| ms_indexable?(d, options) } end documents = group.map do |d| attributes = settings.get_attributes(d) - unless attributes.class == Hash - attributes = attributes.to_hash - end - attributes.merge ms_pk(options) => ms_primary_key_of(d, options) + attributes = attributes.to_hash unless attributes.instance_of?(Hash) + attributes.merge ms_pk(options) => ms_primary_key_of(d, options) end last_update= index.add_documents(documents) end - index.wait_for_pending_update(last_update["updateId"]) if last_update and (synchronous || options[:synchronous]) + index.wait_for_pending_update(last_update['updateId']) if last_update && (synchronous || options[:synchronous]) end nil end def ms_set_settings(synchronous = false) @@ -478,41 +473,44 @@ final_settings = settings.to_settings end index = SafeIndex.new(ms_index_uid(options), true, options) update = index.update_settings(final_settings) - index.wait_for_pending_update(update["updateId"]) if synchronous + index.wait_for_pending_update(update['updateId']) if synchronous end end def ms_index_documents(documents, synchronous = false) ms_configurations.each do |options, settings| next if ms_indexing_disabled?(options) + index = ms_ensure_init(options, settings) update = index.add_documents(documents.map { |d| settings.get_attributes(d).merge ms_pk(options) => ms_primary_key_of(d, options) }) - index.wait_for_pending_update(update["updateId"]) if synchronous || options[:synchronous] + index.wait_for_pending_update(update['updateId']) if synchronous || options[:synchronous] end end def ms_index!(document, synchronous = false) return if ms_without_auto_index_scope + ms_configurations.each do |options, settings| next if ms_indexing_disabled?(options) + primary_key = ms_primary_key_of(document, options) index = ms_ensure_init(options, settings) if ms_indexable?(document, options) - raise ArgumentError.new("Cannot index a record without a primary key") if primary_key.blank? + raise ArgumentError, 'Cannot index a record without a primary key' if primary_key.blank? + + doc = settings.get_attributes(document) + doc = doc.merge ms_pk(options) => primary_key + if synchronous || options[:synchronous] - doc = settings.get_attributes(document) - doc = doc.merge ms_pk(options) => primary_key index.add_documents!(doc) else - doc = settings.get_attributes(document) - doc = doc.merge ms_pk(options) => primary_key index.add_documents(doc) end - elsif ms_conditional_index?(options) && !primary_key.blank? + elsif ms_conditional_index?(options) && primary_key.present? # remove non-indexable documents if synchronous || options[:synchronous] index.delete_document!(primary_key) else index.delete_document(primary_key) @@ -522,14 +520,17 @@ nil end def ms_remove_from_index!(document, synchronous = false) return if ms_without_auto_index_scope + primary_key = ms_primary_key_of(document) - raise ArgumentError.new("Cannot index a record without a primary key") if primary_key.blank? + raise ArgumentError, 'Cannot index a record without a primary key' if primary_key.blank? + ms_configurations.each do |options, settings| next if ms_indexing_disabled?(options) + index = ms_ensure_init(options, settings) if synchronous || options[:synchronous] index.delete_document!(primary_key) else index.delete_document(primary_key) @@ -539,36 +540,40 @@ end def ms_clear_index!(synchronous = false) ms_configurations.each do |options, settings| next if ms_indexing_disabled?(options) + index = ms_ensure_init(options, settings) synchronous || options[:synchronous] ? index.delete_all_documents! : index.delete_all_documents @ms_indexes[settings] = nil end nil end def ms_raw_search(q, params = {}) - index_uid = params.delete(:index) || - params.delete('index') + index_uid = params.delete(:index) || params.delete('index') - if !meilisearch_settings.get_setting(:attributesToHighlight).nil? + unless meilisearch_settings.get_setting(:attributesToHighlight).nil? params[:attributesToHighlight] = meilisearch_settings.get_setting(:attributesToHighlight) end - if !meilisearch_settings.get_setting(:attributesToCrop).nil? + unless meilisearch_settings.get_setting(:attributesToCrop).nil? params[:attributesToCrop] = meilisearch_settings.get_setting(:attributesToCrop) - params[:cropLength] = meilisearch_settings.get_setting(:cropLength) if !meilisearch_settings.get_setting(:cropLength).nil? + + unless meilisearch_settings.get_setting(:cropLength).nil? + params[:cropLength] = meilisearch_settings.get_setting(:cropLength) + end end + index = ms_index(index_uid) - index.search(q, Hash[params.map { |k,v| [k, v] }]) + index.search(q, params.map { |k, v| [k, v] }.to_h) end module AdditionalMethods def self.extended(base) - class <<base + class << base alias_method :raw_answer, :ms_raw_answer unless method_defined? :raw_answer alias_method :facets_distribution, :ms_facets_distribution unless method_defined? :facets_distribution end end @@ -579,16 +584,17 @@ def ms_facets_distribution @ms_json['facetsDistribution'] end private + def ms_init_raw_answer(json) @ms_json = json end end - def ms_search(q, params = {}) + def ms_search(query, params = {}) if MeiliSearch.configuration[:pagination_backend] page = params[:page].nil? ? params[:page] : params[:page].to_i hits_per_page = params[:hitsPerPage].nil? ? params[:hitsPerPage] : params[:hitsPerPage].to_i @@ -596,32 +602,33 @@ params.delete(:hitsPerPage) params[:limit] = 200 end # Returns raw json hits as follows: - # {"hits"=>[{"id"=>"13", "href"=>"apple", "name"=>"iphone"}], "offset"=>0, "limit"=>|| 20, "nbHits"=>1, "exhaustiveNbHits"=>false, "processingTimeMs"=>0, "query"=>"iphone"} - json = ms_raw_search(q, params) + # {"hits"=>[{"id"=>"13", "href"=>"apple", "name"=>"iphone"}], "offset"=>0, "limit"=>|| 20, "nbHits"=>1, + # "exhaustiveNbHits"=>false, "processingTimeMs"=>0, "query"=>"iphone"} + json = ms_raw_search(query, params) # Returns the ids of the hits: 13 hit_ids = json['hits'].map { |hit| hit[ms_pk(meilisearch_options).to_s] } # condition_key gets the primary key of the document; looks for "id" on the options - if defined?(::Mongoid::Document) && self.include?(::Mongoid::Document) - condition_key = ms_primary_key_method.in - else - condition_key = ms_primary_key_method - end + condition_key = if defined?(::Mongoid::Document) && include?(::Mongoid::Document) + ms_primary_key_method.in + else + ms_primary_key_method + end # meilisearch_options[:type] refers to the Model name (e.g. Product) - # results_by_id creates a hash with the primaryKey of the document (id) as the key and the document itself as the value - # {"13"=>#<Product id: 13, name: "iphone", href: "apple", tags: nil, type: nil, description: "Puts even more features at your fingertips", release_date: nil>} + # results_by_id creates a hash with the primaryKey of the document (id) as the key and doc itself as the value + # {"13"=>#<Product id: 13, name: "iphone", href: "apple", tags: nil, type: nil, + # description: "Puts even more features at your fingertips", release_date: nil>} results_by_id = meilisearch_options[:type].where(condition_key => hit_ids).index_by do |hit| ms_primary_key_of(hit) end results = json['hits'].map do |hit| - o = results_by_id[hit[ms_pk(meilisearch_options).to_s].to_s] if o o.formatted = hit['_formatted'] o end @@ -629,40 +636,42 @@ total_hits = json['hits'].length hits_per_page ||= 20 page ||= 1 - res = MeiliSearch::Pagination.create(results, total_hits, meilisearch_options.merge({ page: page , per_page: hits_per_page })) + res = MeiliSearch::Pagination.create(results, total_hits, meilisearch_options.merge(page: page, per_page: hits_per_page)) res.extend(AdditionalMethods) res.send(:ms_init_raw_answer, json) res end def ms_index(name = nil) if name ms_configurations.each do |o, s| return ms_ensure_init(o, s) if o[:index_uid].to_s == name.to_s end - raise ArgumentError.new("Invalid index name: #{name}") + raise ArgumentError, "Invalid index name: #{name}" end ms_ensure_init end def ms_index_uid(options = nil) options ||= meilisearch_options name = options[:index_uid] || model_name.to_s.gsub('::', '_') - name = "#{name}_#{Rails.env.to_s}" if options[:per_environment] + name = "#{name}_#{Rails.env}" if options[:per_environment] name end def ms_must_reindex?(document) # use +ms_dirty?+ method if implemented - return document.send(:ms_dirty?) if (document.respond_to?(:ms_dirty?)) + return document.send(:ms_dirty?) if document.respond_to?(:ms_dirty?) + # Loop over each index to see if a attribute used in records has changed ms_configurations.each do |options, settings| next if ms_indexing_disabled?(options) return true if ms_primary_key_changed?(document, options) + settings.get_attribute_names(document).each do |k| return true if ms_attribute_changed?(document, k) # return true if !document.respond_to?(changed_method) || document.send(changed_method) end [options[:if], options[:unless]].each do |condition| @@ -676,29 +685,30 @@ # let's always reindex then return true end end end + # By default, we don't reindex - return false + false end protected def ms_ensure_init(options = nil, settings = nil, index_settings = nil) - raise ArgumentError.new('No `meilisearch` block found in your model.') if meilisearch_settings.nil? + raise ArgumentError, 'No `meilisearch` block found in your model.' if meilisearch_settings.nil? @ms_indexes ||= {} options ||= meilisearch_options settings ||= meilisearch_settings return @ms_indexes[settings] if @ms_indexes[settings] @ms_indexes[settings] = SafeIndex.new(ms_index_uid(options), meilisearch_options[:raise_on_failure], meilisearch_options) - current_settings = @ms_indexes[settings].settings(:getVersion => 1) rescue nil # if the index doesn't exist + current_settings = @ms_indexes[settings].settings(getVersion: 1) rescue nil # if the index doesn't exist index_settings ||= settings.to_settings index_settings = options[:primary_settings].to_settings.merge(index_settings) if options[:inherit] options[:check_settings] = true if options[:check_settings].nil? @@ -711,21 +721,22 @@ end private def ms_configurations - raise ArgumentError.new('No `meilisearch` block found in your model.') if meilisearch_settings.nil? + raise ArgumentError, 'No `meilisearch` block found in your model.' if meilisearch_settings.nil? + if @configurations.nil? @configurations = {} @configurations[meilisearch_options] = meilisearch_settings - meilisearch_settings.additional_indexes.each do |k,v| + meilisearch_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 + next unless v.additional_indexes.any? + + v.additional_indexes.each do |options, index| + @configurations[options] = index end end end @configurations end @@ -748,17 +759,18 @@ options[:primary_key] || MeiliSearch::IndexSettings::DEFAULT_PRIMARY_KEY end def meilisearch_settings_changed?(prev, current) return true if prev.nil? + current.each do |k, v| prev_v = prev[k.to_s] - if v.is_a?(Array) and prev_v.is_a?(Array) + if v.is_a?(Array) && 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 } - else - return true if prev_v != v + return true if v.map(&:to_s) != prev_v.map(&:to_s) + elsif prev_v != v + return true end end false end @@ -794,15 +806,15 @@ document.send(constraint.to_sym) when Enumerable # All constraints must pass constraint.all? { |inner_constraint| ms_constraint_passes?(document, inner_constraint) } else - if constraint.respond_to?(:call) # Proc - constraint.call(document) - else + unless constraint.respond_to?(:call) raise ArgumentError, "Unknown constraint type: #{constraint} (#{constraint.class})" end + + constraint.call(document) end end def ms_indexing_disabled?(options = nil) options ||= meilisearch_options @@ -828,11 +840,11 @@ 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 + if (items.length % batch_size).zero? yield items items = [] end end yield items unless items.empty? @@ -867,51 +879,56 @@ self.class.ms_remove_from_index!(self, synchronous || ms_synchronous?) end def ms_enqueue_remove_from_index!(synchronous) if meilisearch_options[:enqueue] - meilisearch_options[:enqueue].call(self, true) unless self.class.send(:ms_indexing_disabled?, meilisearch_options) + unless self.class.send(:ms_indexing_disabled?, meilisearch_options) + meilisearch_options[:enqueue].call(self, true) + end else ms_remove_from_index!(synchronous || ms_synchronous?) end end def ms_enqueue_index!(synchronous) if meilisearch_options[:enqueue] - meilisearch_options[:enqueue].call(self, false) unless self.class.send(:ms_indexing_disabled?, meilisearch_options) + unless self.class.send(:ms_indexing_disabled?, meilisearch_options) + meilisearch_options[:enqueue].call(self, false) + end else ms_index!(synchronous) end end - private - def ms_synchronous? - @ms_synchronous == true + @ms_synchronous end + private + def ms_mark_synchronous @ms_synchronous = true end def ms_mark_for_auto_indexing @ms_auto_indexing = true end def ms_mark_must_reindex # ms_must_reindex flag is reset after every commit as part. If we must reindex at any point in - # a stransaction, keep flag set until it is explicitly unset + # a transaction, keep flag set until it is explicitly unset @ms_must_reindex ||= - if defined?(::Sequel) && is_a?(Sequel::Model) - new? || self.class.ms_must_reindex?(self) - else - new_record? || self.class.ms_must_reindex?(self) - end + if defined?(::Sequel) && is_a?(Sequel::Model) + new? || self.class.ms_must_reindex?(self) + else + new_record? || self.class.ms_must_reindex?(self) + end true end def ms_perform_index_tasks return if !@ms_auto_indexing || @ms_must_reindex == false + ms_enqueue_index!(ms_synchronous?) remove_instance_variable(:@ms_auto_indexing) if instance_variable_defined?(:@ms_auto_indexing) remove_instance_variable(:@ms_synchronous) if instance_variable_defined?(:@ms_synchronous) remove_instance_variable(:@ms_must_reindex) if instance_variable_defined?(:@ms_must_reindex) end