module Searchkick

based on gist.github.com/mnutt/566725

thread-local (technically fiber-local) indexer used to aggregate bulk callbacks across models

based on gist.github.com/mnutt/566725

Constants

VERSION

Attributes

aws_credentials[R]
client[W]
client_options[RW]
client_type[RW]
env[W]
index_prefix[RW]
index_suffix[RW]
model_options[RW]
models[RW]
queue_name[RW]
redis[RW]
search_method_name[RW]
search_timeout[W]
timeout[RW]

Public Class Methods

aws_credentials=(creds) click to toggle source
# File lib/searchkick.rb, line 256
def self.aws_credentials=(creds)
  require "faraday_middleware/aws_sigv4"

  @aws_credentials = creds
  @client = nil # reset client
end
callbacks(value = nil, message: nil) { || ... } click to toggle source

message is private

# File lib/searchkick.rb, line 229
def self.callbacks(value = nil, message: nil)
  if block_given?
    previous_value = callbacks_value
    begin
      self.callbacks_value = value
      result = yield
      if callbacks_value == :bulk && indexer.queued_items.any?
        event = {}
        if message
          message.call(event)
        else
          event[:name] = "Bulk"
          event[:count] = indexer.queued_items.size
        end
        ActiveSupport::Notifications.instrument("request.searchkick", event) do
          indexer.perform
        end
      end
      result
    ensure
      self.callbacks_value = previous_value
    end
  else
    self.callbacks_value = value
  end
end
callbacks?(default: true) click to toggle source
# File lib/searchkick.rb, line 220
def self.callbacks?(default: true)
  if callbacks_value.nil?
    default
  else
    callbacks_value != false
  end
end
callbacks_value() click to toggle source

private

# File lib/searchkick.rb, line 328
def self.callbacks_value
  Thread.current[:searchkick_callbacks_enabled]
end
callbacks_value=(value) click to toggle source

private

# File lib/searchkick.rb, line 333
def self.callbacks_value=(value)
  Thread.current[:searchkick_callbacks_enabled] = value
end
client() click to toggle source
# File lib/searchkick.rb, line 71
def self.client
  @client ||= begin
    client_type =
      if self.client_type
        self.client_type
      elsif defined?(OpenSearch::Client) && defined?(Elasticsearch::Client)
        raise Error, "Multiple clients found - set Searchkick.client_type = :elasticsearch or :opensearch"
      elsif defined?(OpenSearch::Client)
        :opensearch
      elsif defined?(Elasticsearch::Client)
        :elasticsearch
      else
        raise Error, "No client found - install the `elasticsearch` or `opensearch-ruby` gem"
      end

    # check after client to ensure faraday is installed
    # TODO remove in Searchkick 6
    if defined?(Typhoeus) && Gem::Version.new(Faraday::VERSION) < Gem::Version.new("0.14.0")
      require "typhoeus/adapters/faraday"
    end

    if client_type == :opensearch
      OpenSearch::Client.new({
        url: ENV["OPENSEARCH_URL"],
        transport_options: {request: {timeout: timeout}, headers: {content_type: "application/json"}},
        retry_on_failure: 2
      }.deep_merge(client_options)) do |f|
        f.use Searchkick::Middleware
        f.request :aws_sigv4, signer_middleware_aws_params if aws_credentials
      end
    else
      raise Error, "The `elasticsearch` gem must be 7+" if Elasticsearch::VERSION.to_i < 7

      Elasticsearch::Client.new({
        url: ENV["ELASTICSEARCH_URL"],
        transport_options: {request: {timeout: timeout}, headers: {content_type: "application/json"}},
        retry_on_failure: 2
      }.deep_merge(client_options)) do |f|
        f.use Searchkick::Middleware
        f.request :aws_sigv4, signer_middleware_aws_params if aws_credentials
      end
    end
  end
end
disable_callbacks() click to toggle source
# File lib/searchkick.rb, line 216
def self.disable_callbacks
  self.callbacks_value = false
end
enable_callbacks() click to toggle source

callbacks

# File lib/searchkick.rb, line 212
def self.enable_callbacks
  self.callbacks_value = nil
end
env() click to toggle source
# File lib/searchkick.rb, line 116
def self.env
  @env ||= ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
end
indexer() click to toggle source

private

# File lib/searchkick.rb, line 323
def self.indexer
  Thread.current[:searchkick_indexer] ||= Indexer.new
end
knn_support?() click to toggle source

private

# File lib/searchkick.rb, line 147
def self.knn_support?
  if opensearch?
    !server_below?("2.4.0", true)
  else
    !server_below?("8.6.0")
  end
end
load_model(class_name, allow_child: false) click to toggle source

public (for reindexing conversions)

# File lib/searchkick.rb, line 307
def self.load_model(class_name, allow_child: false)
  model = class_name.safe_constantize
  raise Error, "Could not find class: #{class_name}" unless model
  if allow_child
    unless model.respond_to?(:searchkick_klass)
      raise Error, "#{class_name} is not a searchkick model"
    end
  else
    unless Searchkick.models.include?(model)
      raise Error, "#{class_name} is not a searchkick model"
    end
  end
  model
end
load_records(relation, ids) click to toggle source

private

# File lib/searchkick.rb, line 290
def self.load_records(relation, ids)
  relation =
    if relation.respond_to?(:primary_key)
      primary_key = relation.primary_key
      raise Error, "Need primary key to load records" if !primary_key

      relation.where(primary_key => ids)
    elsif relation.respond_to?(:queryable)
      relation.queryable.for_ids(ids)
    end

  raise Error, "Not sure how to load records" if !relation

  relation
end
not_allowed_error?(e) click to toggle source

private

# File lib/searchkick.rb, line 381
def self.not_allowed_error?(e)
  (defined?(Elastic::Transport) && e.is_a?(Elastic::Transport::Transport::Errors::MethodNotAllowed)) ||
  (defined?(Elasticsearch::Transport) && e.is_a?(Elasticsearch::Transport::Transport::Errors::MethodNotAllowed)) ||
  (defined?(OpenSearch) && e.is_a?(OpenSearch::Transport::Transport::Errors::MethodNotAllowed))
end
not_found_error?(e) click to toggle source

private

# File lib/searchkick.rb, line 367
def self.not_found_error?(e)
  (defined?(Elastic::Transport) && e.is_a?(Elastic::Transport::Transport::Errors::NotFound)) ||
  (defined?(Elasticsearch::Transport) && e.is_a?(Elasticsearch::Transport::Transport::Errors::NotFound)) ||
  (defined?(OpenSearch) && e.is_a?(OpenSearch::Transport::Transport::Errors::NotFound))
end
opensearch?() click to toggle source
# File lib/searchkick.rb, line 133
def self.opensearch?
  unless defined?(@opensearch)
    @opensearch = server_info["version"]["distribution"] == "opensearch"
  end
  @opensearch
end
reindex_status(index_name) click to toggle source
# File lib/searchkick.rb, line 263
def self.reindex_status(index_name)
  raise Error, "Redis not configured" unless redis

  batches_left = Index.new(index_name).batches_left
  {
    completed: batches_left == 0,
    batches_left: batches_left
  }
end
relation?(klass) click to toggle source

private methods are forwarded to base class this check to see if scope exists on that class it’s a bit tricky, but this seems to work

# File lib/searchkick.rb, line 346
def self.relation?(klass)
  if klass.respond_to?(:current_scope)
    !klass.current_scope.nil?
  else
    klass.is_a?(Mongoid::Criteria) || !Mongoid::Threaded.current_scope(klass).nil?
  end
end
scope(model) click to toggle source

private

# File lib/searchkick.rb, line 355
def self.scope(model)
  # safety check to make sure used properly in code
  raise Error, "Cannot scope relation" if relation?(model)

  if model.searchkick_options[:unscope]
    model.unscoped
  else
    model
  end
end
script(source, **options) click to toggle source

experimental

# File lib/searchkick.rb, line 206
def self.script(source, **options)
  Script.new(source, **options)
end
search_timeout() click to toggle source
# File lib/searchkick.rb, line 120
def self.search_timeout
  (defined?(@search_timeout) && @search_timeout) || timeout
end
server_below?(version, true_version = false) click to toggle source

TODO always check true version in Searchkick 6

# File lib/searchkick.rb, line 141
def self.server_below?(version, true_version = false)
  server_version = !true_version && opensearch? ? "7.10.2" : self.server_version
  Gem::Version.new(server_version.split("-")[0]) < Gem::Version.new(version.split("-")[0])
end
server_info() click to toggle source

private

# File lib/searchkick.rb, line 125
def self.server_info
  @server_info ||= client.info
end
server_version() click to toggle source
# File lib/searchkick.rb, line 129
def self.server_version
  @server_version ||= server_info["version"]["number"]
end
signer_middleware_aws_params() click to toggle source

private

# File lib/searchkick.rb, line 338
def self.signer_middleware_aws_params
  {service: "es", region: "us-east-1"}.merge(aws_credentials)
end
transport_error?(e) click to toggle source

private

# File lib/searchkick.rb, line 374
def self.transport_error?(e)
  (defined?(Elastic::Transport) && e.is_a?(Elastic::Transport::Transport::Error)) ||
  (defined?(Elasticsearch::Transport) && e.is_a?(Elasticsearch::Transport::Transport::Error)) ||
  (defined?(OpenSearch) && e.is_a?(OpenSearch::Transport::Transport::Error))
end
warn(message) click to toggle source
Calls superclass method
# File lib/searchkick.rb, line 285
def self.warn(message)
  super("[searchkick] WARNING: #{message}")
end
with_redis() { |r| ... } click to toggle source
# File lib/searchkick.rb, line 273
def self.with_redis
  if redis
    if redis.respond_to?(:with)
      redis.with do |r|
        yield r
      end
    else
      yield redis
    end
  end
end