Class: Searchkick::Index

Inherits:
Object
  • Object
show all
Defined in:
lib/searchkick/index.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(name, options = {}) ⇒ Index

Returns a new instance of Index.



5
6
7
8
9
# File 'lib/searchkick/index.rb', line 5

def initialize(name, options = {})
  @name = name
  @options = options
  @klass_document_type = {} # cache
end

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



3
4
5
# File 'lib/searchkick/index.rb', line 3

def name
  @name
end

#optionsObject (readonly)

Returns the value of attribute options.



3
4
5
# File 'lib/searchkick/index.rb', line 3

def options
  @options
end

Instance Method Details

#alias_exists?Boolean

Returns:

  • (Boolean)


37
38
39
# File 'lib/searchkick/index.rb', line 37

def alias_exists?
  client.indices.exists_alias name: name
end

#all_indices(unaliased: false) ⇒ Object



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/searchkick/index.rb', line 107

def all_indices(unaliased: false)
  indices =
    begin
      if client.indices.respond_to?(:get_alias)
        client.indices.get_alias(index: "#{name}*")
      else
        client.indices.get_aliases
      end
    rescue => e
      raise e unless Searchkick.not_found_error?(e)
      {}
    end
  indices = indices.select { |_k, v| v.empty? || v["aliases"].empty? } if unaliased
  indices.select { |k, _v| k =~ /\A#{Regexp.escape(name)}_\d{14,17}\z/ }.keys
end

#batches_leftObject



269
270
271
# File 'lib/searchkick/index.rb', line 269

def batches_left
  relation_indexer.batches_left
end

#bulk_delete(records) ⇒ Object



150
151
152
153
154
155
156
# File 'lib/searchkick/index.rb', line 150

def bulk_delete(records)
  return if records.empty?

  notify_bulk(records, "Delete") do
    queue_delete(records)
  end
end

#bulk_index(records) ⇒ Object Also known as: import



158
159
160
161
162
163
164
# File 'lib/searchkick/index.rb', line 158

def bulk_index(records)
  return if records.empty?

  notify_bulk(records, "Import") do
    queue_index(records)
  end
end

#bulk_update(records, method_name) ⇒ Object



167
168
169
170
171
172
173
# File 'lib/searchkick/index.rb', line 167

def bulk_update(records, method_name)
  return if records.empty?

  notify_bulk(records, "Update") do
    queue_update(records, method_name)
  end
end

#clean_indicesObject

remove old indices that start w/ index_name



124
125
126
127
128
129
130
# File 'lib/searchkick/index.rb', line 124

def clean_indices
  indices = all_indices(unaliased: true)
  indices.each do |index|
    Index.new(index).delete
  end
  indices
end

#conversions_fieldsObject

private



287
288
289
290
291
292
# File 'lib/searchkick/index.rb', line 287

def conversions_fields
  @conversions_fields ||= begin
    conversions = Array(options[:conversions])
    conversions.map(&:to_s) + conversions.map(&:to_sym)
  end
end

#create(body = {}) ⇒ Object



15
16
17
# File 'lib/searchkick/index.rb', line 15

def create(body = {})
  client.indices.create index: name, body: body
end

#create_index(index_options: nil) ⇒ Object



258
259
260
261
262
263
# File 'lib/searchkick/index.rb', line 258

def create_index(index_options: nil)
  index_options ||= self.index_options
  index = Index.new("#{name}_#{Time.now.strftime('%Y%m%d%H%M%S%L')}", @options)
  index.create(index_options)
  index
end

#deleteObject



19
20
21
22
23
24
25
26
27
# File 'lib/searchkick/index.rb', line 19

def delete
  if alias_exists?
    # can't call delete directly on aliases in ES 6
    indices = client.indices.get_alias(name: name).keys
    client.indices.delete index: indices
  else
    client.indices.delete index: name
  end
end

#document_type(record) ⇒ Object



179
180
181
# File 'lib/searchkick/index.rb', line 179

def document_type(record)
  RecordData.new(self, record).document_type
end

#exists?Boolean

Returns:

  • (Boolean)


29
30
31
# File 'lib/searchkick/index.rb', line 29

def exists?
  client.indices.exists index: name
end

#import_scope(relation, **options) ⇒ Object



265
266
267
# File 'lib/searchkick/index.rb', line 265

def import_scope(relation, **options)
  relation_indexer.reindex(relation, **options)
end

#index_optionsObject



11
12
13
# File 'lib/searchkick/index.rb', line 11

def index_options
  IndexOptions.new(self).index_options
end

#klass_document_type(klass, ignore_type = false) ⇒ Object

private



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

def klass_document_type(klass, ignore_type = false)
  @klass_document_type[[klass, ignore_type]] ||= begin
    if !ignore_type && klass.searchkick_klass.searchkick_options[:_type]
      type = klass.searchkick_klass.searchkick_options[:_type]
      type = type.call if type.respond_to?(:call)
      type
    else
      klass.model_name.to_s.underscore
    end
  end
end

#locations_fieldsObject

private



300
301
302
303
304
305
# File 'lib/searchkick/index.rb', line 300

def locations_fields
  @locations_fields ||= begin
    locations = Array(options[:locations])
    locations.map(&:to_s) + locations.map(&:to_sym)
  end
end

#mappingObject

call to_h for consistent results between elasticsearch gem 7 and 8 could do for all API calls, but just do for ones where return value is focus for now



43
44
45
# File 'lib/searchkick/index.rb', line 43

def mapping
  client.indices.get_mapping(index: name).to_h
end

#promote(new_name, update_refresh_interval: false) ⇒ Object Also known as: swap



78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/searchkick/index.rb', line 78

def promote(new_name, update_refresh_interval: false)
  if update_refresh_interval
    new_index = Index.new(new_name, @options)
    settings = options[:settings] || {}
    refresh_interval = (settings[:index] && settings[:index][:refresh_interval]) || "1s"
    new_index.update_settings(index: {refresh_interval: refresh_interval})
  end

  old_indices =
    begin
      client.indices.get_alias(name: name).keys
    rescue => e
      raise e unless Searchkick.not_found_error?(e)
      {}
    end
  actions = old_indices.map { |old_name| {remove: {index: old_name, alias: name}} } + [{add: {index: new_name, alias: name}}]
  client.indices.update_aliases body: {actions: actions}
end

#refreshObject



33
34
35
# File 'lib/searchkick/index.rb', line 33

def refresh
  client.indices.refresh index: name
end

#refresh_intervalObject



52
53
54
# File 'lib/searchkick/index.rb', line 52

def refresh_interval
  index_settings["refresh_interval"]
end

#reindex(object, method_name: nil, full: false, **options) ⇒ Object

note: this is designed to be used internally so it does not check object matches index class



215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/searchkick/index.rb', line 215

def reindex(object, method_name: nil, full: false, **options)
  if object.is_a?(Array)
    # note: purposefully skip full
    return reindex_records(object, method_name: method_name, **options)
  end

  if !object.respond_to?(:searchkick_klass)
    raise Error, "Cannot reindex object"
  end

  scoped = Searchkick.relation?(object)
  # call searchkick_klass for inheritance
  relation = scoped ? object.all : Searchkick.scope(object.searchkick_klass).all

  refresh = options.fetch(:refresh, !scoped)
  options.delete(:refresh)

  if method_name || (scoped && !full)
    mode = options.delete(:mode) || :inline
    raise ArgumentError, "unsupported keywords: #{options.keys.map(&:inspect).join(", ")}" if options.any?

    # import only
    import_scope(relation, method_name: method_name, mode: mode)
    self.refresh if refresh
    true
  else
    async = options.delete(:async)
    if async
      if async.is_a?(Hash) && async[:wait]
        # TODO warn in 5.1
        # Searchkick.warn "async option is deprecated - use mode: :async, wait: true instead"
        options[:wait] = true unless options.key?(:wait)
      else
        # TODO warn in 5.1
        # Searchkick.warn "async option is deprecated - use mode: :async instead"
      end
      options[:mode] ||= :async
    end

    full_reindex(relation, **options)
  end
end

#reindex_queueObject

queue



207
208
209
# File 'lib/searchkick/index.rb', line 207

def reindex_queue
  ReindexQueue.new(name)
end

#reload_synonymsObject



191
192
193
194
195
196
197
198
199
200
201
202
203
# File 'lib/searchkick/index.rb', line 191

def reload_synonyms
  if Searchkick.opensearch?
    client.transport.perform_request "POST", "_plugins/_refresh_search_analyzers/#{CGI.escape(name)}"
  else
    raise Error, "Requires Elasticsearch 7.3+" if Searchkick.server_below?("7.3.0")
    begin
      client.transport.perform_request("GET", "#{CGI.escape(name)}/_reload_search_analyzers")
    rescue => e
      raise Error, "Requires non-OSS version of Elasticsearch" if Searchkick.not_allowed_error?(e)
      raise e
    end
  end
end

#remove(record) ⇒ Object



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

def remove(record)
  notify(record, "Remove") do
    queue_delete([record])
  end
end

#retrieve(record) ⇒ Object



98
99
100
101
102
103
104
105
# File 'lib/searchkick/index.rb', line 98

def retrieve(record)
  record_data = RecordData.new(self, record).record_data

  # remove underscore
  get_options = record_data.to_h { |k, v| [k.to_s.delete_prefix("_").to_sym, v] }

  client.get(get_options)["_source"]
end

#search_id(record) ⇒ Object



175
176
177
# File 'lib/searchkick/index.rb', line 175

def search_id(record)
  RecordData.new(self, record).search_id
end

#settingsObject

call to_h for consistent results between elasticsearch gem 7 and 8



48
49
50
# File 'lib/searchkick/index.rb', line 48

def settings
  client.indices.get_settings(index: name).to_h
end

#similar_record(record, **options) ⇒ Object



183
184
185
186
187
188
189
# File 'lib/searchkick/index.rb', line 183

def similar_record(record, **options)
  options[:per_page] ||= 10
  options[:similar] = [RecordData.new(self, record).record_data]
  options[:models] ||= [record.class] unless options.key?(:model)

  Searchkick.search("*", **options)
end

#store(record) ⇒ Object



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

def store(record)
  notify(record, "Store") do
    queue_index([record])
  end
end

#suggest_fieldsObject

private



295
296
297
# File 'lib/searchkick/index.rb', line 295

def suggest_fields
  @suggest_fields ||= Array(options[:suggest]).map(&:to_s)
end

#tokens(text, options = {}) ⇒ Object



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

def tokens(text, options = {})
  client.indices.analyze(body: {text: text}.merge(options), index: name)["tokens"].map { |t| t["token"] }
end

#total_docsObject



64
65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/searchkick/index.rb', line 64

def total_docs
  response =
    client.search(
      index: name,
      body: {
        query: {match_all: {}},
        size: 0,
        track_total_hits: true
      }
    )

  Results.new(nil, response).total_count
end

#update_record(record, method_name) ⇒ Object



144
145
146
147
148
# File 'lib/searchkick/index.rb', line 144

def update_record(record, method_name)
  notify(record, "Update") do
    queue_update([record], method_name)
  end
end

#update_settings(settings) ⇒ Object



56
57
58
# File 'lib/searchkick/index.rb', line 56

def update_settings(settings)
  client.indices.put_settings index: name, body: settings
end

#uuidObject

private



308
309
310
# File 'lib/searchkick/index.rb', line 308

def uuid
  index_settings["uuid"]
end