Class: Searchkick::Index

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

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from IndexOptions

#index_options

Constructor Details

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

Returns a new instance of Index.



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

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

Instance Attribute Details

#nameObject (readonly)

Returns the value of attribute name.



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

def name
  @name
end

#optionsObject (readonly)

Returns the value of attribute options.



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

def options
  @options
end

Instance Method Details

#alias_exists?Boolean

Returns:

  • (Boolean)


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

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

#all_indices(options = {}) ⇒ Object



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

def all_indices(options = {})
  indices =
    begin
      client.indices.get_aliases
    rescue Elasticsearch::Transport::Transport::Errors::NotFound
      {}
    end
  indices = indices.select { |_k, v| v.empty? || v["aliases"].empty? } if options[:unaliased]
  indices.select { |k, _v| k =~ /\A#{Regexp.escape(name)}_\d{14,17}\z/ }.keys
end

#bulk_delete(records) ⇒ Object



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

def bulk_delete(records)
  Searchkick.queue_items(records.reject { |r| r.id.blank? }.map { |r| {delete: record_data(r)} })
end

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



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

def bulk_index(records)
  Searchkick.queue_items(records.map { |r| {index: record_data(r).merge(data: search_data(r))} })
end

#clean_indicesObject

remove old indices that start w/ index_name



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

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

#create(options = {}) ⇒ Object



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

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

#create_index(options = {}) ⇒ Object

reindex



140
141
142
143
144
145
# File 'lib/searchkick/index.rb', line 140

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

#deleteObject



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

def delete
  client.indices.delete index: name
end

#exists?Boolean

Returns:

  • (Boolean)


20
21
22
# File 'lib/searchkick/index.rb', line 20

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

#import_scope(scope, options = {}) ⇒ Object



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
# File 'lib/searchkick/index.rb', line 218

def import_scope(scope, options = {})
  batch_size = @options[:batch_size] || 1000

  # use scope for import
  scope = scope.search_import if scope.respond_to?(:search_import)
  if scope.respond_to?(:find_in_batches)
    if options[:resume]
      # use total docs instead of max id since there's not a great way
      # to get the max _id without scripting since it's a string

      # TODO use primary key and prefix with table name
      scope = scope.where("id > ?", total_docs)
    end

    scope.find_in_batches batch_size: batch_size do |batch|
      import batch.select(&:should_index?)
    end
  else
    # https://github.com/karmi/tire/blob/master/lib/tire/model/import.rb
    # use cursor for Mongoid
    items = []
    # TODO add resume
    scope.all.each do |item|
      items << item if item.should_index?
      if items.length == batch_size
        import items
        items = []
      end
    end
    import items
  end
end

#klass_document_type(klass) ⇒ Object



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

def klass_document_type(klass)
  if klass.respond_to?(:document_type)
    klass.document_type
  else
    klass.model_name.to_s.underscore
  end
end

#mappingObject



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

def mapping
  client.indices.get_mapping index: name
end

#record_data(r) ⇒ Object



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

def record_data(r)
  data = {
    _index: name,
    _id: search_id(r),
    _type: document_type(r)
  }
  data[:_routing] = r.search_routing if r.respond_to?(:search_routing)
  data
end

#refreshObject



24
25
26
# File 'lib/searchkick/index.rb', line 24

def refresh
  client.indices.refresh index: name
end

#reindex_record(record) ⇒ Object



84
85
86
87
88
89
90
91
92
93
94
# File 'lib/searchkick/index.rb', line 84

def reindex_record(record)
  if record.destroyed? || !record.should_index?
    begin
      remove(record)
    rescue Elasticsearch::Transport::Transport::Errors::NotFound
      # do nothing
    end
  else
    store(record)
  end
end

#reindex_record_async(record) ⇒ Object



96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/searchkick/index.rb', line 96

def reindex_record_async(record)
  if Searchkick.callbacks_value.nil?
    if defined?(Searchkick::ReindexV2Job)
      Searchkick::ReindexV2Job.perform_later(record.class.name, record.id.to_s)
    elsif defined?(Delayed::Job)
      Delayed::Job.enqueue Searchkick::ReindexJob.new(record.class.name, record.id.to_s)
    else
      raise Searchkick::Error, "Job adapter not found"
    end
  else
    reindex_record(record)
  end
end

#reindex_scope(scope, options = {}) ⇒ Object



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# File 'lib/searchkick/index.rb', line 183

def reindex_scope(scope, options = {})
  skip_import = options[:import] == false
  resume = options[:resume]

  if resume
    index_name = all_indices.sort.last
    raise Searchkick::Error, "No index to resume" unless index_name
    index = Searchkick::Index.new(index_name)
  else
    clean_indices

    index = create_index(index_options: scope.searchkick_index_options)
  end

  # check if alias exists
  if alias_exists?
    # import before swap
    index.import_scope(scope, resume: resume) unless skip_import

    # get existing indices to remove
    swap(index.name)
    clean_indices
  else
    delete if exists?
    swap(index.name)

    # import after swap
    index.import_scope(scope, resume: resume) unless skip_import
  end

  index.refresh

  true
end

#remove(record) ⇒ Object



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

def remove(record)
  bulk_delete([record])
end

#retrieve(record) ⇒ Object



76
77
78
79
80
81
82
# File 'lib/searchkick/index.rb', line 76

def retrieve(record)
  client.get(
    index: name,
    type: document_type(record),
    id: search_id(record)
  )["_source"]
end

#search_model(searchkick_klass, term = nil, options = {}) {|query.body| ... } ⇒ Object

search

Yields:

  • (query.body)


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

def search_model(searchkick_klass, term = nil, options = {}, &block)
  query = Searchkick::Query.new(searchkick_klass, term, options)
  yield(query.body) if block
  if options[:execute] == false
    query
  else
    query.execute
  end
end

#similar_record(record, options = {}) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/searchkick/index.rb', line 110

def similar_record(record, options = {})
  like_text = retrieve(record).to_hash
    .keep_if { |k, _| !options[:fields] || options[:fields].map(&:to_s).include?(k) }
    .values.compact.join(" ")

  # TODO deep merge method
  options[:where] ||= {}
  options[:where][:_id] ||= {}
  options[:where][:_id][:not] = record.id.to_s
  options[:per_page] ||= 10
  options[:similar] = true

  # TODO use index class instead of record class
  search_model(record.class, like_text, options)
end

#store(record) ⇒ Object

record based



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

def store(record)
  bulk_index([record])
end

#swap(new_name) ⇒ Object



36
37
38
39
40
41
42
43
44
45
# File 'lib/searchkick/index.rb', line 36

def swap(new_name)
  old_indices =
    begin
      client.indices.get_alias(name: name).keys
    rescue Elasticsearch::Transport::Transport::Errors::NotFound
      {}
    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

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

other



253
254
255
# File 'lib/searchkick/index.rb', line 253

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

#total_docsObject



167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/searchkick/index.rb', line 167

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

  response["hits"]["total"]
end