module MeiliSearch::ClassMethods

these are the class methods added when MeiliSearch is included

Public Class Methods

extended(base) click to toggle source
# File lib/meilisearch-rails.rb, line 307
def self.extended(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!
    alias_method :clear_index!, :ms_clear_index! unless method_defined? :clear_index!
    alias_method :search, :ms_search unless method_defined? :search
    alias_method :raw_search, :ms_raw_search unless method_defined? :raw_search
    alias_method :index, :ms_index unless method_defined? :index
    alias_method :index_uid, :ms_index_uid unless method_defined? :index_uid
    alias_method :must_reindex?, :ms_must_reindex? unless method_defined? :must_reindex?
  end

  base.cattr_accessor :meilisearch_options, :meilisearch_settings
end

Public Instance Methods

meilisearch(options = {}, &block) click to toggle source
Calls superclass method
# File lib/meilisearch-rails.rb, line 325
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)

  attr_accessor :formatted

  if options[:synchronous] == true
    if defined?(::Sequel) && 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
          ms_mark_synchronous
        end
      end
    else
      after_validation :ms_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|
      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.call(record, remove) unless ms_without_auto_index_scope
    end
  end
  unless options[:auto_index] == false
    if defined?(::Sequel) && 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
          ms_mark_must_reindex
        end

        define_method(:before_save) do |*args|
          copy_before_save.bind(self).call
          ms_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
            ms_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
              ms_perform_index_tasks
            end
          end
        end
      end
    else
      after_validation :ms_mark_must_reindex if respond_to?(:after_validation)
      before_save :ms_mark_for_auto_indexing if respond_to?(:before_save)
      if respond_to?(:after_commit)
        after_commit :ms_perform_index_tasks
      elsif respond_to?(:after_save)
        after_save :ms_perform_index_tasks
      end
    end
  end
  unless options[:auto_remove] == false
    if defined?(::Sequel) && 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
          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)
    end
  end
end
ms_clear_index!(synchronous = false) click to toggle source
# File lib/meilisearch-rails.rb, line 541
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
ms_index(name = nil) click to toggle source
# File lib/meilisearch-rails.rb, line 640
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}")
  end
  ms_ensure_init
end
ms_index!(document, synchronous = false) click to toggle source
# File lib/meilisearch-rails.rb, line 496
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?
      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?
      # remove non-indexable documents
      if synchronous || options[:synchronous]
        index.delete_document!(primary_key)
      else
        index.delete_document(primary_key)
      end
    end
  end
  nil
end
ms_index_documents(documents, synchronous = false) click to toggle source
# File lib/meilisearch-rails.rb, line 487
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]
  end
end
ms_index_uid(options = nil) click to toggle source
# File lib/meilisearch-rails.rb, line 650
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
end
ms_must_reindex?(document) click to toggle source
# File lib/meilisearch-rails.rb, line 657
def ms_must_reindex?(document)
  # use +ms_dirty?+ method if implemented
  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|
      case condition
      when nil
      when String, Symbol
        return true if ms_attribute_changed?(document, condition)
      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
ms_reindex!(batch_size = MeiliSearch::IndexSettings::DEFAULT_BATCH_SIZE, synchronous = false) click to toggle source
# File lib/meilisearch-rails.rb, line 443
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? })
        # 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)
      end
      last_update= index.add_documents(documents)
    end
    index.wait_for_pending_update(last_update["updateId"]) if last_update and (synchronous || options[:synchronous])
  end
  nil
end
ms_remove_from_index!(document, synchronous = false) click to toggle source
# File lib/meilisearch-rails.rb, line 525
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?
  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)
    end
  end
  nil
end
ms_set_settings(synchronous = false) click to toggle source
# File lib/meilisearch-rails.rb, line 472
def ms_set_settings(synchronous = false)
  ms_configurations.each do |options, settings|
    if options[:primary_settings] && options[:inherit]
      primary = options[:primary_settings].to_settings
      final_settings = primary.merge(settings.to_settings)
    else
      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
  end
end
ms_without_auto_index() { || ... } click to toggle source
# File lib/meilisearch-rails.rb, line 426
def ms_without_auto_index(&block)
  self.ms_without_auto_index_scope = true
  begin
    yield
  ensure
    self.ms_without_auto_index_scope = false
  end
end
ms_without_auto_index_scope() click to toggle source
# File lib/meilisearch-rails.rb, line 439
def ms_without_auto_index_scope
  Thread.current["ms_without_auto_index_scope_for_#{self.model_name}"]
end
ms_without_auto_index_scope=(value) click to toggle source
# File lib/meilisearch-rails.rb, line 435
def ms_without_auto_index_scope=(value)
  Thread.current["ms_without_auto_index_scope_for_#{self.model_name}"] = value
end

Protected Instance Methods

ms_ensure_init(options = nil, settings = nil, index_settings = nil) click to toggle source
# File lib/meilisearch-rails.rb, line 687
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?

  @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

  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?

  if !ms_indexing_disabled?(options) && options[:check_settings] && meilisearch_settings_changed?(current_settings, index_settings)
    @ms_indexes[settings].update_settings(index_settings)
  end

  @ms_indexes[settings]
end

Private Instance Methods

meilisearch_settings_changed?(prev, current) click to toggle source
# File lib/meilisearch-rails.rb, line 751
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)
      # 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
    end
  end
  false
end
ms_attribute_changed?(document, attr_name) click to toggle source
# File lib/meilisearch-rails.rb, line 842
def ms_attribute_changed?(document, attr_name)
  if document.respond_to?("will_save_change_to_#{attr_name}?")
    return document.send("will_save_change_to_#{attr_name}?")
  end

  # We don't know if the attribute has changed, so conservatively assume it has
  true
end
ms_conditional_index?(options = nil) click to toggle source
# File lib/meilisearch-rails.rb, line 777
def ms_conditional_index?(options = nil)
  options ||= meilisearch_options
  options[:if].present? || options[:unless].present?
end
ms_configurations() click to toggle source
# File lib/meilisearch-rails.rb, line 715
def ms_configurations
  raise ArgumentError.new('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|
      @configurations[k] = v

      if v.additional_indexes.any?
        v.additional_indexes.each do |options, index|
          @configurations[options] = index
        end
      end
    end
  end
  @configurations
end
ms_constraint_passes?(document, constraint) click to toggle source
# File lib/meilisearch-rails.rb, line 789
def ms_constraint_passes?(document, constraint)
  case constraint
  when Symbol
    document.send(constraint)
  when String
    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
      raise ArgumentError, "Unknown constraint type: #{constraint} (#{constraint.class})"
    end
  end
end
ms_find_in_batches(batch_size) { |items| ... } click to toggle source
# File lib/meilisearch-rails.rb, line 823
def ms_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) && 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
ms_full_const_get(name) click to toggle source
# File lib/meilisearch-rails.rb, line 765
def ms_full_const_get(name)
  list = name.split('::')
  list.shift if list.first.blank?
  obj = 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
ms_indexable?(document, options = nil) click to toggle source
# File lib/meilisearch-rails.rb, line 782
def ms_indexable?(document, options = nil)
  options ||= meilisearch_options
  if_passes = options[:if].blank? || ms_constraint_passes?(document, options[:if])
  unless_passes = options[:unless].blank? || !ms_constraint_passes?(document, options[:unless])
  if_passes && unless_passes
end
ms_indexing_disabled?(options = nil) click to toggle source
# File lib/meilisearch-rails.rb, line 807
def ms_indexing_disabled?(options = nil)
  options ||= meilisearch_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
ms_pk(options = nil) click to toggle source
# File lib/meilisearch-rails.rb, line 747
def ms_pk(options = nil)
  options[:primary_key] || MeiliSearch::IndexSettings::DEFAULT_PRIMARY_KEY
end
ms_primary_key_changed?(doc, options = nil) click to toggle source
# File lib/meilisearch-rails.rb, line 742
def ms_primary_key_changed?(doc, options = nil)
  changed = ms_attribute_changed?(doc, ms_primary_key_method(options))
  changed.nil? ? false : changed
end
ms_primary_key_method(options = nil) click to toggle source
# File lib/meilisearch-rails.rb, line 733
def ms_primary_key_method(options = nil)
  options ||= meilisearch_options
  options[:primary_key] || options[:id] || :id
end
ms_primary_key_of(doc, options = nil) click to toggle source
# File lib/meilisearch-rails.rb, line 738
def ms_primary_key_of(doc, options = nil)
  doc.send(ms_primary_key_method(options)).to_s
end