elasticsearch-model 7.0.0.pre → 7.1.2.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.md +41 -43
- data/Rakefile +7 -10
- data/elasticsearch-model.gemspec +37 -39
- data/examples/activerecord_associations.rb +16 -0
- data/gemfiles/4.0.gemfile +4 -3
- data/gemfiles/6.0.gemfile +3 -3
- data/lib/elasticsearch/model.rb +10 -55
- data/lib/elasticsearch/model/adapters/active_record.rb +1 -1
- data/lib/elasticsearch/model/importing.rb +43 -16
- data/lib/elasticsearch/model/indexing.rb +41 -36
- data/lib/elasticsearch/model/multimodel.rb +1 -1
- data/lib/elasticsearch/model/naming.rb +1 -10
- data/lib/elasticsearch/model/proxy.rb +41 -21
- data/lib/elasticsearch/model/response/records.rb +0 -1
- data/lib/elasticsearch/model/version.rb +1 -1
- data/spec/elasticsearch/model/adapters/active_record/import_spec.rb +1 -12
- data/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb +11 -11
- data/spec/elasticsearch/model/importing_spec.rb +12 -0
- data/spec/elasticsearch/model/indexing_spec.rb +3 -33
- data/spec/elasticsearch/model/module_spec.rb +1 -0
- data/spec/spec_helper.rb +19 -5
- data/spec/support/app.rb +9 -2
- metadata +46 -48
- data/spec/elasticsearch/model/naming_inheritance_spec.rb +0 -123
@@ -102,7 +102,7 @@ module Elasticsearch
|
|
102
102
|
scope = scope.__send__(named_scope) if named_scope
|
103
103
|
scope = scope.instance_exec(&query) if query
|
104
104
|
|
105
|
-
scope.find_in_batches(options) do |batch|
|
105
|
+
scope.find_in_batches(**options) do |batch|
|
106
106
|
batch = self.__send__(preprocess, batch) if preprocess
|
107
107
|
yield(batch) if batch.present?
|
108
108
|
end
|
@@ -53,7 +53,29 @@ module Elasticsearch
|
|
53
53
|
#
|
54
54
|
# @yield [Hash] Gives the Hash with the Elasticsearch response to the block
|
55
55
|
#
|
56
|
-
# @return [Fixnum]
|
56
|
+
# @return [Fixnum] default, number of errors encountered during importing
|
57
|
+
# @return [Array<Hash>] if +return+ option is specified to be +"errors"+,
|
58
|
+
# contains only those failed items in the response +items+ key, e.g.:
|
59
|
+
#
|
60
|
+
# [
|
61
|
+
# {
|
62
|
+
# "index" => {
|
63
|
+
# "error" => 'FAILED',
|
64
|
+
# "_index" => "test",
|
65
|
+
# "_type" => "_doc",
|
66
|
+
# "_id" => '1',
|
67
|
+
# "_version" => 1,
|
68
|
+
# "result" => "foo",
|
69
|
+
# "_shards" => {
|
70
|
+
# "total" => 1,
|
71
|
+
# "successful" => 0,
|
72
|
+
# "failed" => 1
|
73
|
+
# },
|
74
|
+
# "status" => 400
|
75
|
+
# }
|
76
|
+
# }
|
77
|
+
# ]
|
78
|
+
#
|
57
79
|
#
|
58
80
|
# @example Import all records into the index
|
59
81
|
#
|
@@ -99,20 +121,19 @@ module Elasticsearch
|
|
99
121
|
#
|
100
122
|
# @example Update the batch before yielding it
|
101
123
|
#
|
102
|
-
#
|
103
|
-
#
|
104
|
-
#
|
105
|
-
#
|
106
|
-
#
|
107
|
-
#
|
108
|
-
#
|
109
|
-
#
|
110
|
-
#
|
124
|
+
# class Article
|
125
|
+
# # ...
|
126
|
+
# def self.enrich(batch)
|
127
|
+
# batch.each do |item|
|
128
|
+
# item.metadata = MyAPI.get_metadata(item.id)
|
129
|
+
# end
|
130
|
+
# batch
|
131
|
+
# end
|
132
|
+
# end
|
111
133
|
#
|
112
134
|
# Article.import preprocess: :enrich
|
113
135
|
#
|
114
|
-
# @example Return an array of error elements instead of the number of errors,
|
115
|
-
# to try importing these records again
|
136
|
+
# @example Return an array of error elements instead of the number of errors, e.g. to try importing these records again
|
116
137
|
#
|
117
138
|
# Article.import return: 'errors'
|
118
139
|
#
|
@@ -122,6 +143,7 @@ module Elasticsearch
|
|
122
143
|
target_index = options.delete(:index) || index_name
|
123
144
|
target_type = options.delete(:type) || document_type
|
124
145
|
transform = options.delete(:transform) || __transform
|
146
|
+
pipeline = options.delete(:pipeline)
|
125
147
|
return_value = options.delete(:return) || 'count'
|
126
148
|
|
127
149
|
unless transform.respond_to?(:call)
|
@@ -137,10 +159,15 @@ module Elasticsearch
|
|
137
159
|
end
|
138
160
|
|
139
161
|
__find_in_batches(options) do |batch|
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
162
|
+
params = {
|
163
|
+
index: target_index,
|
164
|
+
type: target_type,
|
165
|
+
body: __batch_to_bulk(batch, transform)
|
166
|
+
}
|
167
|
+
|
168
|
+
params[:pipeline] = pipeline if pipeline
|
169
|
+
|
170
|
+
response = client.bulk params
|
144
171
|
|
145
172
|
yield response if block_given?
|
146
173
|
|
@@ -30,7 +30,7 @@ module Elasticsearch
|
|
30
30
|
#
|
31
31
|
module Indexing
|
32
32
|
|
33
|
-
# Wraps the [index settings](
|
33
|
+
# Wraps the [index settings](https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html)
|
34
34
|
#
|
35
35
|
class Settings
|
36
36
|
attr_accessor :settings
|
@@ -48,7 +48,7 @@ module Elasticsearch
|
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
-
# Wraps the [index mappings](
|
51
|
+
# Wraps the [index mappings](https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping.html)
|
52
52
|
#
|
53
53
|
class Mappings
|
54
54
|
attr_accessor :options, :type
|
@@ -270,7 +270,7 @@ module Elasticsearch
|
|
270
270
|
def index_exists?(options={})
|
271
271
|
target_index = options[:index] || self.index_name
|
272
272
|
|
273
|
-
self.client.indices.exists(index: target_index
|
273
|
+
self.client.indices.exists(index: target_index, ignore: 404)
|
274
274
|
end
|
275
275
|
|
276
276
|
# Deletes the index with corresponding name
|
@@ -308,7 +308,7 @@ module Elasticsearch
|
|
308
308
|
#
|
309
309
|
# Article.__elasticsearch__.refresh_index! index: 'my-index'
|
310
310
|
#
|
311
|
-
# @see
|
311
|
+
# @see https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html
|
312
312
|
#
|
313
313
|
def refresh_index!(options={})
|
314
314
|
target_index = options.delete(:index) || self.index_name
|
@@ -338,13 +338,17 @@ module Elasticsearch
|
|
338
338
|
#
|
339
339
|
# @see #update_document
|
340
340
|
#
|
341
|
-
base.before_save do |
|
342
|
-
if
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
341
|
+
base.before_save do |obj|
|
342
|
+
if obj.respond_to?(:changes_to_save) # Rails 5.1
|
343
|
+
changes_to_save = obj.changes_to_save
|
344
|
+
elsif obj.respond_to?(:changes)
|
345
|
+
changes_to_save = obj.changes
|
346
|
+
end
|
347
|
+
|
348
|
+
if changes_to_save
|
349
|
+
attrs = obj.instance_variable_get(:@__changed_model_attributes) || {}
|
350
|
+
latest_changes = changes_to_save.inject({}) { |latest_changes, (k,v)| latest_changes.merge!(k => v.last) }
|
351
|
+
obj.instance_variable_set(:@__changed_model_attributes, attrs.merge(latest_changes))
|
348
352
|
end
|
349
353
|
end if base.respond_to?(:before_save)
|
350
354
|
end
|
@@ -364,14 +368,13 @@ module Elasticsearch
|
|
364
368
|
# @see http://rubydoc.info/gems/elasticsearch-api/Elasticsearch/API/Actions:index
|
365
369
|
#
|
366
370
|
def index_document(options={})
|
367
|
-
document =
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
)
|
371
|
+
document = as_indexed_json
|
372
|
+
request = { index: index_name,
|
373
|
+
id: id,
|
374
|
+
body: document }
|
375
|
+
request.merge!(type: document_type) if document_type
|
376
|
+
|
377
|
+
client.index(request.merge!(options))
|
375
378
|
end
|
376
379
|
|
377
380
|
# Deletes the model instance from the index
|
@@ -388,11 +391,11 @@ module Elasticsearch
|
|
388
391
|
# @see http://rubydoc.info/gems/elasticsearch-api/Elasticsearch/API/Actions:delete
|
389
392
|
#
|
390
393
|
def delete_document(options={})
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
)
|
394
|
+
request = { index: index_name,
|
395
|
+
id: self.id }
|
396
|
+
request.merge!(type: document_type) if document_type
|
397
|
+
|
398
|
+
client.delete(request.merge!(options))
|
396
399
|
end
|
397
400
|
|
398
401
|
# Tries to gather the changed attributes of a model instance
|
@@ -427,12 +430,14 @@ module Elasticsearch
|
|
427
430
|
attributes_in_database
|
428
431
|
end
|
429
432
|
|
430
|
-
|
431
|
-
{ index: index_name,
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
433
|
+
unless attributes.empty?
|
434
|
+
request = { index: index_name,
|
435
|
+
id: self.id,
|
436
|
+
body: { doc: attributes } }
|
437
|
+
request.merge!(type: document_type) if document_type
|
438
|
+
|
439
|
+
client.update(request.merge!(options))
|
440
|
+
end
|
436
441
|
else
|
437
442
|
index_document(options)
|
438
443
|
end
|
@@ -453,12 +458,12 @@ module Elasticsearch
|
|
453
458
|
# @return [Hash] The response from Elasticsearch
|
454
459
|
#
|
455
460
|
def update_document_attributes(attributes, options={})
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
)
|
461
|
+
request = { index: index_name,
|
462
|
+
id: self.id,
|
463
|
+
body: { doc: attributes } }
|
464
|
+
request.merge!(type: document_type) if document_type
|
465
|
+
|
466
|
+
client.update(request.merge!(options))
|
462
467
|
end
|
463
468
|
end
|
464
469
|
|
@@ -85,7 +85,7 @@ module Elasticsearch
|
|
85
85
|
# @return [Array] the list of document types used for retrieving documents
|
86
86
|
#
|
87
87
|
def document_type
|
88
|
-
models.map { |m| m.document_type }
|
88
|
+
models.map { |m| m.document_type }.compact.presence
|
89
89
|
end
|
90
90
|
|
91
91
|
# Get the client common for all models
|
@@ -92,16 +92,7 @@ module Elasticsearch
|
|
92
92
|
private
|
93
93
|
|
94
94
|
def implicit(prop)
|
95
|
-
|
96
|
-
|
97
|
-
if Elasticsearch::Model.settings[:inheritance_enabled]
|
98
|
-
self.ancestors.each do |klass|
|
99
|
-
next if klass == self
|
100
|
-
break if value = klass.respond_to?(prop) && klass.send(prop)
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
value || self.send("default_#{prop}")
|
95
|
+
self.send("default_#{prop}")
|
105
96
|
end
|
106
97
|
|
107
98
|
def default_index_name
|
@@ -19,15 +19,15 @@ module Elasticsearch
|
|
19
19
|
module Model
|
20
20
|
|
21
21
|
# This module provides a proxy interfacing between the including class and
|
22
|
-
#
|
22
|
+
# `Elasticsearch::Model`, preventing the pollution of the including class namespace.
|
23
23
|
#
|
24
24
|
# The only "gateway" between the model and Elasticsearch::Model is the
|
25
|
-
#
|
25
|
+
# `#__elasticsearch__` class and instance method.
|
26
26
|
#
|
27
27
|
# The including class must be compatible with
|
28
28
|
# [ActiveModel](https://github.com/rails/rails/tree/master/activemodel).
|
29
29
|
#
|
30
|
-
# @example Include the
|
30
|
+
# @example Include the `Elasticsearch::Model` module into an `Article` model
|
31
31
|
#
|
32
32
|
# class Article < ActiveRecord::Base
|
33
33
|
# include Elasticsearch::Model
|
@@ -53,21 +53,19 @@ module Elasticsearch
|
|
53
53
|
# module and the functionality is accessible via the proxy.
|
54
54
|
#
|
55
55
|
def self.included(base)
|
56
|
+
|
56
57
|
base.class_eval do
|
57
|
-
|
58
|
-
#
|
58
|
+
|
59
|
+
# `ClassMethodsProxy` instance, accessed as `MyModel.__elasticsearch__`
|
59
60
|
def self.__elasticsearch__ &block
|
60
61
|
@__elasticsearch__ ||= ClassMethodsProxy.new(self)
|
61
62
|
@__elasticsearch__.instance_eval(&block) if block_given?
|
62
63
|
@__elasticsearch__
|
63
64
|
end
|
64
65
|
|
65
|
-
#
|
66
|
-
|
67
|
-
|
68
|
-
@__elasticsearch__ ||= InstanceMethodsProxy.new(self)
|
69
|
-
@__elasticsearch__.instance_eval(&block) if block_given?
|
70
|
-
@__elasticsearch__
|
66
|
+
# Mix the importing module into the `ClassMethodsProxy`
|
67
|
+
self.__elasticsearch__.class_eval do
|
68
|
+
include Adapter.from_class(base).importing_mixin
|
71
69
|
end
|
72
70
|
|
73
71
|
# Register a callback for storing changed attributes for models which implement
|
@@ -75,18 +73,28 @@ module Elasticsearch
|
|
75
73
|
#
|
76
74
|
# @see http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
|
77
75
|
#
|
78
|
-
before_save do |
|
79
|
-
if
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
76
|
+
before_save do |obj|
|
77
|
+
if obj.respond_to?(:changes_to_save) # Rails 5.1
|
78
|
+
changes_to_save = obj.changes_to_save
|
79
|
+
elsif obj.respond_to?(:changes)
|
80
|
+
changes_to_save = obj.changes
|
81
|
+
end
|
82
|
+
|
83
|
+
if changes_to_save
|
84
|
+
attrs = obj.__elasticsearch__.instance_variable_get(:@__changed_model_attributes) || {}
|
85
|
+
latest_changes = changes_to_save.inject({}) { |latest_changes, (k,v)| latest_changes.merge!(k => v.last) }
|
86
|
+
obj.__elasticsearch__.instance_variable_set(:@__changed_model_attributes, attrs.merge(latest_changes))
|
87
87
|
end
|
88
88
|
end if respond_to?(:before_save)
|
89
89
|
end
|
90
|
+
|
91
|
+
# {InstanceMethodsProxy}, accessed as `@mymodel.__elasticsearch__`
|
92
|
+
#
|
93
|
+
def __elasticsearch__ &block
|
94
|
+
@__elasticsearch__ ||= InstanceMethodsProxy.new(self)
|
95
|
+
@__elasticsearch__.instance_eval(&block) if block_given?
|
96
|
+
@__elasticsearch__
|
97
|
+
end
|
90
98
|
end
|
91
99
|
|
92
100
|
# @overload dup
|
@@ -130,6 +138,11 @@ module Elasticsearch
|
|
130
138
|
#
|
131
139
|
class ClassMethodsProxy
|
132
140
|
include Base
|
141
|
+
include Elasticsearch::Model::Client::ClassMethods
|
142
|
+
include Elasticsearch::Model::Naming::ClassMethods
|
143
|
+
include Elasticsearch::Model::Indexing::ClassMethods
|
144
|
+
include Elasticsearch::Model::Searching::ClassMethods
|
145
|
+
include Elasticsearch::Model::Importing::ClassMethods
|
133
146
|
end
|
134
147
|
|
135
148
|
# A proxy interfacing between Elasticsearch::Model instance methods and model instance methods
|
@@ -138,6 +151,10 @@ module Elasticsearch
|
|
138
151
|
#
|
139
152
|
class InstanceMethodsProxy
|
140
153
|
include Base
|
154
|
+
include Elasticsearch::Model::Client::InstanceMethods
|
155
|
+
include Elasticsearch::Model::Naming::InstanceMethods
|
156
|
+
include Elasticsearch::Model::Indexing::InstanceMethods
|
157
|
+
include Elasticsearch::Model::Serializing::InstanceMethods
|
141
158
|
|
142
159
|
def klass
|
143
160
|
target.class
|
@@ -153,8 +170,11 @@ module Elasticsearch
|
|
153
170
|
def as_json(options={})
|
154
171
|
target.as_json(options)
|
155
172
|
end
|
156
|
-
end
|
157
173
|
|
174
|
+
def as_indexed_json(options={})
|
175
|
+
target.respond_to?(:as_indexed_json) ? target.__send__(:as_indexed_json, options) : super
|
176
|
+
end
|
177
|
+
end
|
158
178
|
end
|
159
179
|
end
|
160
180
|
end
|
@@ -18,7 +18,6 @@
|
|
18
18
|
require 'spec_helper'
|
19
19
|
|
20
20
|
describe 'Elasticsearch::Model::Adapter::ActiveRecord Importing' do
|
21
|
-
|
22
21
|
before(:all) do
|
23
22
|
ActiveRecord::Schema.define(:version => 1) do
|
24
23
|
create_table :import_articles do |t|
|
@@ -43,11 +42,9 @@ describe 'Elasticsearch::Model::Adapter::ActiveRecord Importing' do
|
|
43
42
|
end
|
44
43
|
|
45
44
|
describe '#import' do
|
46
|
-
|
47
45
|
context 'when no search criteria is specified' do
|
48
|
-
|
49
46
|
before do
|
50
|
-
10.times { |i| ImportArticle.create! title: 'Test', views:
|
47
|
+
10.times { |i| ImportArticle.create! title: 'Test', views: i.to_s }
|
51
48
|
ImportArticle.import
|
52
49
|
ImportArticle.__elasticsearch__.refresh_index!
|
53
50
|
end
|
@@ -58,7 +55,6 @@ describe 'Elasticsearch::Model::Adapter::ActiveRecord Importing' do
|
|
58
55
|
end
|
59
56
|
|
60
57
|
context 'when batch size is specified' do
|
61
|
-
|
62
58
|
before do
|
63
59
|
10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" }
|
64
60
|
end
|
@@ -82,7 +78,6 @@ describe 'Elasticsearch::Model::Adapter::ActiveRecord Importing' do
|
|
82
78
|
end
|
83
79
|
|
84
80
|
context 'when a scope is specified' do
|
85
|
-
|
86
81
|
before do
|
87
82
|
10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" }
|
88
83
|
ImportArticle.import(scope: 'popular', force: true)
|
@@ -95,7 +90,6 @@ describe 'Elasticsearch::Model::Adapter::ActiveRecord Importing' do
|
|
95
90
|
end
|
96
91
|
|
97
92
|
context 'when a query is specified' do
|
98
|
-
|
99
93
|
before do
|
100
94
|
10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" }
|
101
95
|
ImportArticle.import(query: -> { where('views >= 3') })
|
@@ -108,7 +102,6 @@ describe 'Elasticsearch::Model::Adapter::ActiveRecord Importing' do
|
|
108
102
|
end
|
109
103
|
|
110
104
|
context 'when there are invalid documents' do
|
111
|
-
|
112
105
|
let!(:result) do
|
113
106
|
10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" }
|
114
107
|
new_article
|
@@ -132,7 +125,6 @@ describe 'Elasticsearch::Model::Adapter::ActiveRecord Importing' do
|
|
132
125
|
end
|
133
126
|
|
134
127
|
context 'when a transform proc is specified' do
|
135
|
-
|
136
128
|
before do
|
137
129
|
10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" }
|
138
130
|
ImportArticle.import( transform: ->(a) {{ index: { data: { name: a.title, foo: 'BAR' } }}} )
|
@@ -151,7 +143,6 @@ describe 'Elasticsearch::Model::Adapter::ActiveRecord Importing' do
|
|
151
143
|
end
|
152
144
|
|
153
145
|
context 'when the model has a default scope' do
|
154
|
-
|
155
146
|
around(:all) do |example|
|
156
147
|
10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" }
|
157
148
|
ImportArticle.instance_eval { default_scope { where('views > 3') } }
|
@@ -170,7 +161,6 @@ describe 'Elasticsearch::Model::Adapter::ActiveRecord Importing' do
|
|
170
161
|
end
|
171
162
|
|
172
163
|
context 'when there is a default scope and a query specified' do
|
173
|
-
|
174
164
|
around(:all) do |example|
|
175
165
|
10.times { |i| ImportArticle.create! title: 'Test', views: "#{i}" }
|
176
166
|
ImportArticle.instance_eval { default_scope { where('views > 3') } }
|
@@ -189,7 +179,6 @@ describe 'Elasticsearch::Model::Adapter::ActiveRecord Importing' do
|
|
189
179
|
end
|
190
180
|
|
191
181
|
context 'when the batch is empty' do
|
192
|
-
|
193
182
|
before do
|
194
183
|
ImportArticle.delete_all
|
195
184
|
ImportArticle.import
|