Module: Searchkick

Defined in:
lib/searchkick/indexer.rb,
lib/searchkick.rb,
lib/searchkick/index.rb,
lib/searchkick/model.rb,
lib/searchkick/query.rb,
lib/searchkick/where.rb,
lib/searchkick/results.rb,
lib/searchkick/version.rb,
lib/searchkick/relation.rb,
lib/searchkick/middleware.rb,
lib/searchkick/index_cache.rb,
lib/searchkick/record_data.rb,
lib/searchkick/hash_wrapper.rb,
lib/searchkick/multi_search.rb,
lib/searchkick/index_options.rb,
lib/searchkick/reindex_queue.rb,
lib/searchkick/log_subscriber.rb,
lib/searchkick/record_indexer.rb,
lib/searchkick/reindex_v2_job.rb,
lib/searchkick/bulk_reindex_job.rb,
lib/searchkick/relation_indexer.rb,
lib/searchkick/process_batch_job.rb,
lib/searchkick/process_queue_job.rb,
lib/searchkick/controller_runtime.rb

Overview

Defined Under Namespace

Modules: ControllerRuntime, Model Classes: BulkReindexJob, DangerousOperation, Error, HashWrapper, ImportError, Index, IndexCache, IndexOptions, Indexer, InvalidQueryError, LogSubscriber, Middleware, MissingIndexError, MultiSearch, ProcessBatchJob, ProcessQueueJob, Query, RecordData, RecordIndexer, ReindexQueue, ReindexV2Job, Relation, RelationIndexer, Results, UnsupportedVersionError, Where

Constant Summary collapse

VERSION =
"5.3.1"

Class Attribute Summary collapse

Class Method Summary collapse

Class Attribute Details

.aws_credentialsObject

Returns the value of attribute aws_credentials.



60
61
62
# File 'lib/searchkick.rb', line 60

def aws_credentials
  @aws_credentials
end

.clientObject



69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/searchkick.rb', line 69

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

.client_optionsObject

Returns the value of attribute client_options.



58
59
60
# File 'lib/searchkick.rb', line 58

def client_options
  @client_options
end

.client_typeObject

Returns the value of attribute client_type.



58
59
60
# File 'lib/searchkick.rb', line 58

def client_type
  @client_type
end

.envObject



114
115
116
# File 'lib/searchkick.rb', line 114

def self.env
  @env ||= ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
end

.index_prefixObject

Returns the value of attribute index_prefix.



58
59
60
# File 'lib/searchkick.rb', line 58

def index_prefix
  @index_prefix
end

.index_suffixObject

Returns the value of attribute index_suffix.



58
59
60
# File 'lib/searchkick.rb', line 58

def index_suffix
  @index_suffix
end

.model_optionsObject

Returns the value of attribute model_options.



58
59
60
# File 'lib/searchkick.rb', line 58

def model_options
  @model_options
end

.modelsObject

Returns the value of attribute models.



58
59
60
# File 'lib/searchkick.rb', line 58

def models
  @models
end

.queue_nameObject

Returns the value of attribute queue_name.



58
59
60
# File 'lib/searchkick.rb', line 58

def queue_name
  @queue_name
end

.redisObject

Returns the value of attribute redis.



58
59
60
# File 'lib/searchkick.rb', line 58

def redis
  @redis
end

.search_method_nameObject

Returns the value of attribute search_method_name.



58
59
60
# File 'lib/searchkick.rb', line 58

def search_method_name
  @search_method_name
end

.search_timeoutObject



118
119
120
# File 'lib/searchkick.rb', line 118

def self.search_timeout
  (defined?(@search_timeout) && @search_timeout) || timeout
end

.timeoutObject

Returns the value of attribute timeout.



58
59
60
# File 'lib/searchkick.rb', line 58

def timeout
  @timeout
end

Class Method Details

.callbacks(value = nil, message: nil) ⇒ Object

message is private



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
# File 'lib/searchkick.rb', line 211

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) ⇒ Boolean

Returns:

  • (Boolean)


202
203
204
205
206
207
208
# File 'lib/searchkick.rb', line 202

def self.callbacks?(default: true)
  if callbacks_value.nil?
    default
  else
    callbacks_value != false
  end
end

.callbacks_valueObject

private



310
311
312
# File 'lib/searchkick.rb', line 310

def self.callbacks_value
  Thread.current[:searchkick_callbacks_enabled]
end

.callbacks_value=(value) ⇒ Object

private



315
316
317
# File 'lib/searchkick.rb', line 315

def self.callbacks_value=(value)
  Thread.current[:searchkick_callbacks_enabled] = value
end

.disable_callbacksObject



198
199
200
# File 'lib/searchkick.rb', line 198

def self.disable_callbacks
  self.callbacks_value = false
end

.enable_callbacksObject

callbacks



194
195
196
# File 'lib/searchkick.rb', line 194

def self.enable_callbacks
  self.callbacks_value = nil
end

.indexerObject

private



305
306
307
# File 'lib/searchkick.rb', line 305

def self.indexer
  Thread.current[:searchkick_indexer] ||= Indexer.new
end

.load_model(class_name, allow_child: false) ⇒ Object

public (for reindexing conversions)

Raises:



289
290
291
292
293
294
295
296
297
298
299
300
301
302
# File 'lib/searchkick.rb', line 289

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) ⇒ Object

private

Raises:



272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
# File 'lib/searchkick.rb', line 272

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

.multi_search(queries) ⇒ Object



179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/searchkick.rb', line 179

def self.multi_search(queries)
  return if queries.empty?

  queries = queries.map { |q| q.send(:query) }
  event = {
    name: "Multi Search",
    body: queries.flat_map { |q| [q.params.except(:body).to_json, q.body.to_json] }.map { |v| "#{v}\n" }.join,
  }
  ActiveSupport::Notifications.instrument("multi_search.searchkick", event) do
    MultiSearch.new(queries).perform
  end
end

.not_allowed_error?(e) ⇒ Boolean

private

Returns:

  • (Boolean)


363
364
365
366
367
# File 'lib/searchkick.rb', line 363

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) ⇒ Boolean

private

Returns:

  • (Boolean)


349
350
351
352
353
# File 'lib/searchkick.rb', line 349

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?Boolean

Returns:

  • (Boolean)


131
132
133
134
135
136
# File 'lib/searchkick.rb', line 131

def self.opensearch?
  unless defined?(@opensearch)
    @opensearch = server_info["version"]["distribution"] == "opensearch"
  end
  @opensearch
end

.reindex_status(index_name) ⇒ Object

Raises:



245
246
247
248
249
250
251
252
253
# File 'lib/searchkick.rb', line 245

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) ⇒ Boolean

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

Returns:

  • (Boolean)


328
329
330
331
332
333
334
# File 'lib/searchkick.rb', line 328

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) ⇒ Object

private

Raises:



337
338
339
340
341
342
343
344
345
346
# File 'lib/searchkick.rb', line 337

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

.search(term = "*", model: nil, **options, &block) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/searchkick.rb', line 144

def self.search(term = "*", model: nil, **options, &block)
  options = options.dup
  klass = model

  # convert index_name into models if possible
  # this should allow for easier upgrade
  if options[:index_name] && !options[:models] && Array(options[:index_name]).all? { |v| v.respond_to?(:searchkick_index) }
    options[:models] = options.delete(:index_name)
  end

  # make Searchkick.search(models: [Product]) and Product.search equivalent
  unless klass
    models = Array(options[:models])
    if models.size == 1
      klass = models.first
      options.delete(:models)
    end
  end

  if klass
    if (options[:models] && Array(options[:models]) != [klass]) || Array(options[:index_name]).any? { |v| v.respond_to?(:searchkick_index) && v != klass }
      raise ArgumentError, "Use Searchkick.search to search multiple models"
    end
  end

  # TODO remove in Searchkick 6
  if options[:execute] == false
    Searchkick.warn("The execute option is no longer needed")
    options.delete(:execute)
  end

  options = options.merge(block: block) if block
  Relation.new(klass, term, **options)
end

.server_below?(version, true_version = false) ⇒ Boolean

TODO always check true version in Searchkick 6

Returns:

  • (Boolean)


139
140
141
142
# File 'lib/searchkick.rb', line 139

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_infoObject

private



123
124
125
# File 'lib/searchkick.rb', line 123

def self.server_info
  @server_info ||= client.info
end

.server_versionObject



127
128
129
# File 'lib/searchkick.rb', line 127

def self.server_version
  @server_version ||= server_info["version"]["number"]
end

.signer_middleware_aws_paramsObject

private



320
321
322
# File 'lib/searchkick.rb', line 320

def self.signer_middleware_aws_params
  {service: "es", region: "us-east-1"}.merge(aws_credentials)
end

.transport_error?(e) ⇒ Boolean

private

Returns:

  • (Boolean)


356
357
358
359
360
# File 'lib/searchkick.rb', line 356

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) ⇒ Object



267
268
269
# File 'lib/searchkick.rb', line 267

def self.warn(message)
  super("[searchkick] WARNING: #{message}")
end

.with_redisObject



255
256
257
258
259
260
261
262
263
264
265
# File 'lib/searchkick.rb', line 255

def self.with_redis
  if redis
    if redis.respond_to?(:with)
      redis.with do |r|
        yield r
      end
    else
      yield redis
    end
  end
end