elasticsearch-model-extensions 0.2.2 → 0.3.0
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/.travis.yml +3 -0
- data/Gemfile +2 -1
- data/README.md +5 -0
- data/gemfiles/rails41.gemfile +11 -0
- data/gemfiles/rails41.gemfile.lock +109 -0
- data/lib/elasticsearch/model/extensions/association_path_finding/association_path_finder.rb +19 -0
- data/lib/elasticsearch/model/extensions/association_path_finding/mapping_node.rb +68 -0
- data/lib/elasticsearch/model/extensions/association_path_finding/shortest_path.rb +108 -0
- data/lib/elasticsearch/model/extensions/batch_updating.rb +9 -52
- data/lib/elasticsearch/model/extensions/batch_updating/batch_updater.rb +68 -0
- data/lib/elasticsearch/model/extensions/configuration.rb +33 -5
- data/lib/elasticsearch/model/extensions/dependency_tracking.rb +17 -21
- data/lib/elasticsearch/model/extensions/dependency_tracking/dependency_tracker.rb +52 -0
- data/lib/elasticsearch/model/extensions/mapping_reflection.rb +7 -80
- data/lib/elasticsearch/model/extensions/mapping_reflection/mapping_reflector.rb +107 -0
- data/lib/elasticsearch/model/extensions/outer_document_updating.rb +0 -21
- data/lib/elasticsearch/model/extensions/partial_updating.rb +16 -116
- data/lib/elasticsearch/model/extensions/partial_updating/partial_updater.rb +149 -0
- data/lib/elasticsearch/model/extensions/proxy.rb +0 -0
- data/lib/elasticsearch/model/extensions/update_callback.rb +3 -2
- data/lib/elasticsearch/model/extensions/version.rb +1 -1
- data/spec/batch_updating/batch_updater_spec.rb +50 -0
- data/spec/batch_updating_spec.rb +134 -0
- data/spec/integration_spec.rb +350 -4
- data/spec/partial_updating_spec.rb +36 -6
- data/spec/setup/articles_with_comments.rb +2 -4
- data/spec/setup/articles_with_comments_and_delayed_jobs.rb +2 -4
- data/spec/setup/authors_and_books_with_tags.rb +183 -0
- data/spec/setup/items_and_categories.rb +0 -0
- data/spec/setup/sqlite.rb +1 -1
- data/spec/setup/undefine.rb +6 -6
- data/spec/spec_helper.rb +6 -0
- metadata +20 -4
- data/lib/elasticsearch/model/extensions/mapping_node.rb +0 -65
- data/lib/elasticsearch/model/extensions/shortest_path.rb +0 -106
data/spec/integration_spec.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
require 'elasticsearch/model/extensions/all'
|
2
2
|
|
3
|
-
RSpec.shared_examples 'a
|
3
|
+
RSpec.shared_examples 'a search supporting automatic as_indexed_json creation' do
|
4
4
|
describe 'a record creation' do
|
5
5
|
before(:each) do
|
6
6
|
::Article.create(title: 'foo', created_at: Time.now)
|
@@ -42,6 +42,318 @@ RSpec.shared_examples 'a normal elasticsearch-model object' do
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
+
RSpec.shared_examples 'a search supporting association' do
|
46
|
+
describe 'a record creation' do
|
47
|
+
before(:each) do
|
48
|
+
::Article.create(title: 'foo', created_at: Time.now, comments_attributes: [{body: 'new_comment'}])
|
49
|
+
|
50
|
+
::Article.__elasticsearch__.refresh_index!
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'makes the document searchable' do
|
54
|
+
expect(Article.search('foo').records.size).to eq(1)
|
55
|
+
expect(Article.search('new_comment').records.size).to eq(1)
|
56
|
+
expect(Article.search(query: {term: {'comments.body' => 'new_comment'}}).records.size).to eq(1)
|
57
|
+
expect(Article.search(query: {term: {'num_comments' => 1}}).records.size).to eq(2)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'a record update' do
|
62
|
+
before(:each) do
|
63
|
+
Article.first.update_attributes title: 'Test2', comments_attributes: [{body: 'new_comment'}]
|
64
|
+
|
65
|
+
Article.__elasticsearch__.refresh_index!
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'makes the document unsearchable using the old content' do
|
69
|
+
expect(Article.search('Test').records.size).to eq(0)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'makes the document searchable using the new content' do
|
73
|
+
expect(Article.search('Test2').records.size).to eq(1)
|
74
|
+
expect(Article.search('new_comment').records.size).to eq(1)
|
75
|
+
expect(Article.search(query: {term: {'comments.body' => 'new_comment'}}).records.size).to eq(1)
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'updates the dependent field' do
|
79
|
+
expect(Article.search(query: {term: {'num_comments' => 1}}).records.size).to eq(2)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe 'a record deletion' do
|
84
|
+
before(:each) do
|
85
|
+
Article.first.destroy
|
86
|
+
|
87
|
+
Article.__elasticsearch__.refresh_index!
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'makes the document unsearchable' do
|
91
|
+
expect(Article.search('Test').records.size).to eq(0)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
context 'when an article\'s comment updated' do
|
96
|
+
def updating_articles_comment
|
97
|
+
Article.last.tap do |a|
|
98
|
+
a.comments.where(body: 'Comment1').first.tap do |c|
|
99
|
+
c.body = 'Comment2'
|
100
|
+
c.save!
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
Article.__elasticsearch__.refresh_index!
|
105
|
+
end
|
106
|
+
|
107
|
+
def number_of_articles_found_for_the_old_comment
|
108
|
+
Article.search('Comment1').records.size
|
109
|
+
end
|
110
|
+
|
111
|
+
def number_of_articles_found_for_the_new_comment
|
112
|
+
Article.search('Comment2').records.size
|
113
|
+
end
|
114
|
+
|
115
|
+
it 'makes the article unsearchable with the old comment' do
|
116
|
+
expect { updating_articles_comment }.to change { number_of_articles_found_for_the_old_comment }.by(-1)
|
117
|
+
end
|
118
|
+
|
119
|
+
it 'makes the article searchable with the new comment' do
|
120
|
+
expect { updating_articles_comment }.to change { number_of_articles_found_for_the_new_comment }.by(1)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
context 'when an article\'s comment destroyed' do
|
125
|
+
def destroying_articles_comment
|
126
|
+
Article.last.tap do |a|
|
127
|
+
a.comments.where(body: 'Comment1').first.tap do |c|
|
128
|
+
c.destroy
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
Article.__elasticsearch__.refresh_index!
|
133
|
+
end
|
134
|
+
|
135
|
+
def number_of_articles_found_for_the_comment
|
136
|
+
Article.search('Comment1').records.size
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'makes the article unsearchable with the old comment' do
|
140
|
+
expect { destroying_articles_comment }.to change { number_of_articles_found_for_the_comment }.by(-1)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
RSpec.shared_examples 'a search supporting polymorphic associations' do
|
146
|
+
def find_author_by_nickname(name)
|
147
|
+
if Author.respond_to?(:find_by)
|
148
|
+
# Rails 4
|
149
|
+
Author.find_by(nickname: name)
|
150
|
+
else
|
151
|
+
# Rails 3
|
152
|
+
Author.find_by_nickname(name)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def find_book_by_title(title)
|
157
|
+
if Book.respond_to?(:find_by)
|
158
|
+
Book.find_by(title: title)
|
159
|
+
else
|
160
|
+
Book.find_by_title(title)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context 'when an author is created' do
|
165
|
+
before(:each) do
|
166
|
+
::Author.create(nickname: 'Touma', created_at: Time.now, books_attributes: [{title: 'new_book'}])
|
167
|
+
|
168
|
+
::Author.__elasticsearch__.refresh_index!
|
169
|
+
end
|
170
|
+
|
171
|
+
it 'makes the author\'s document searchable' do
|
172
|
+
expect(Author.search('Touma').records.size).to eq(1)
|
173
|
+
expect(Author.search('new_book').records.size).to eq(1)
|
174
|
+
expect(Author.search(query: {term: {'books.title' => 'new_book'}}).records.size).to eq(1)
|
175
|
+
expect(Author.search(query: {term: {'num_books' => 2}}).records.size).to eq(1)
|
176
|
+
expect(Author.search(query: {term: {'num_books' => 1}}).records.size).to eq(2)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
context 'when one of the books is updated' do
|
181
|
+
def updating_the_book_with_new_title
|
182
|
+
find_book_by_title('Code').update_attributes title: 'Think'
|
183
|
+
|
184
|
+
Author.__elasticsearch__.refresh_index!
|
185
|
+
Book.__elasticsearch__.refresh_index!
|
186
|
+
end
|
187
|
+
|
188
|
+
def number_of_authors_found_for_the_new_title
|
189
|
+
Author.search('Think').records.size
|
190
|
+
end
|
191
|
+
|
192
|
+
def number_of_books_found_for_the_new_title
|
193
|
+
Book.search('Think').records.size
|
194
|
+
end
|
195
|
+
|
196
|
+
it 'makes the author\'s document searchable' do
|
197
|
+
expect { updating_the_book_with_new_title }.to change { number_of_authors_found_for_the_new_title }.by(1)
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'makes the book\'s document searchable' do
|
201
|
+
expect { updating_the_book_with_new_title }.to change { number_of_books_found_for_the_new_title }.by(1)
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
context 'when one of the books is destroyed' do
|
206
|
+
def destroying_the_book
|
207
|
+
find_book_by_title('Test').destroy
|
208
|
+
|
209
|
+
Author.__elasticsearch__.refresh_index!
|
210
|
+
Book.__elasticsearch__.refresh_index!
|
211
|
+
end
|
212
|
+
|
213
|
+
def number_of_authors_found
|
214
|
+
Author.search('Test').records.size
|
215
|
+
end
|
216
|
+
|
217
|
+
def number_of_books_found
|
218
|
+
Book.search('Test').records.size
|
219
|
+
end
|
220
|
+
|
221
|
+
it 'makes the author having the book unsearchable' do
|
222
|
+
expect { destroying_the_book }.to change { number_of_authors_found }.by(-1)
|
223
|
+
end
|
224
|
+
|
225
|
+
it 'makes the book unsearchable' do
|
226
|
+
expect { destroying_the_book }.to change { number_of_books_found }.by(-1)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
context 'when the author is updated' do
|
231
|
+
before(:each) do
|
232
|
+
find_author_by_nickname('Kuroko').update_attributes nickname: 'Test2', books_attributes: [{title: 'new_book'}]
|
233
|
+
|
234
|
+
Author.__elasticsearch__.refresh_index!
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'makes the document unsearchable using the old content' do
|
238
|
+
expect(Author.search('Kuroko').records.size).to eq(0)
|
239
|
+
expect(Author.search(query: {term: {'num_books' => 2}}).records.size).to eq(0)
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'makes the document searchable using the new content' do
|
243
|
+
expect(Author.search('Test2').records.size).to eq(1)
|
244
|
+
expect(Author.search('new_book').records.size).to eq(1)
|
245
|
+
expect(Author.search(query: {term: {'books.title' => 'new_book'}}).records.size).to eq(1)
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'updates the dependent field' do
|
249
|
+
expect(Author.search(query: {term: {'num_books' => 1}}).records.size).to eq(1)
|
250
|
+
expect(Author.search(query: {term: {'num_books' => 3}}).records.size).to eq(1)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
context 'when the author is deleted' do
|
255
|
+
before(:each) do
|
256
|
+
find_author_by_nickname('Kuroko').destroy
|
257
|
+
|
258
|
+
Author.__elasticsearch__.refresh_index!
|
259
|
+
end
|
260
|
+
|
261
|
+
it 'makes the document unsearchable' do
|
262
|
+
expect(Author.search('Kuroko').records.size).to eq(0)
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
266
|
+
context 'when an author\'s tag is updated' do
|
267
|
+
def updating_an_authors_tag
|
268
|
+
tag = find_author_by_nickname('Kuroko').tags.where(body: 'coding').first
|
269
|
+
tag.tap do |t|
|
270
|
+
t.body = 'thinking'
|
271
|
+
t.save!
|
272
|
+
end
|
273
|
+
|
274
|
+
Author.__elasticsearch__.refresh_index!
|
275
|
+
end
|
276
|
+
|
277
|
+
def number_of_authors_found_for_the_old_tag
|
278
|
+
Author.search('coding').records.size
|
279
|
+
end
|
280
|
+
|
281
|
+
def number_of_authors_found_for_the_new_tag
|
282
|
+
Author.search('thinking').records.size
|
283
|
+
end
|
284
|
+
|
285
|
+
it 'makes the author unsearchable with the old tag' do
|
286
|
+
expect { updating_an_authors_tag }.to change { number_of_authors_found_for_the_old_tag }.by(-1)
|
287
|
+
end
|
288
|
+
|
289
|
+
it 'makes the author seachable with the new tag' do
|
290
|
+
expect { updating_an_authors_tag }.to change { number_of_authors_found_for_the_new_tag }.by(1)
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
context 'when an author\'s tag is destroyed' do
|
295
|
+
def destroying_a_tag_for_an_author
|
296
|
+
tag = find_author_by_nickname('Kuroko').tags.where(body: 'coding').first
|
297
|
+
tag.destroy
|
298
|
+
|
299
|
+
Author.__elasticsearch__.refresh_index!
|
300
|
+
end
|
301
|
+
|
302
|
+
def number_of_authors_found_for_the_tag
|
303
|
+
Author.search('coding').records.size
|
304
|
+
end
|
305
|
+
|
306
|
+
it 'makes an author unsearchable' do
|
307
|
+
expect { destroying_a_tag_for_an_author }.to change { number_of_authors_found_for_the_tag }.by(-1)
|
308
|
+
end
|
309
|
+
end
|
310
|
+
|
311
|
+
context 'when an book\'s tag is destroyed' do
|
312
|
+
def destroying_a_books_tag
|
313
|
+
find_book_by_title('Test').tags.where(body: 'testing').first.destroy
|
314
|
+
|
315
|
+
Book.__elasticsearch__.refresh_index!
|
316
|
+
end
|
317
|
+
|
318
|
+
def number_of_books_found_for_the_tag
|
319
|
+
Book.search('testing').records.size
|
320
|
+
end
|
321
|
+
|
322
|
+
it 'makes the book unsearchable' do
|
323
|
+
expect { destroying_a_books_tag }.to change { number_of_books_found_for_the_tag }.by(-1)
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
context 'when an book\'s tag is updated' do
|
328
|
+
def updating_a_books_tag_with_a_new_body
|
329
|
+
find_book_by_title('Test').tags.where(body: 'testing').first.tap do |t|
|
330
|
+
t.body = 'thinking'
|
331
|
+
t.save!
|
332
|
+
end
|
333
|
+
|
334
|
+
Book.__elasticsearch__.refresh_index!
|
335
|
+
end
|
336
|
+
|
337
|
+
def number_of_books_found_for_the_old_tag
|
338
|
+
Book.search('testing').records.size
|
339
|
+
end
|
340
|
+
|
341
|
+
def number_of_books_found_for_the_new_tag
|
342
|
+
Book.search('thinking').records.size
|
343
|
+
end
|
344
|
+
|
345
|
+
it 'makes the book unsearchable with the old tag' do
|
346
|
+
expect { updating_a_books_tag_with_a_new_body }.to change { number_of_books_found_for_the_old_tag }.by(-1)
|
347
|
+
end
|
348
|
+
|
349
|
+
it 'makes the book searchable with the new tag' do
|
350
|
+
expect { updating_a_books_tag_with_a_new_body }.to change { number_of_books_found_for_the_new_tag }.by(1)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
# TODO Describe about logical deletion
|
355
|
+
end
|
356
|
+
|
45
357
|
RSpec.describe 'integration' do
|
46
358
|
context 'with articles' do
|
47
359
|
before :each do
|
@@ -54,10 +366,26 @@ RSpec.describe 'integration' do
|
|
54
366
|
end
|
55
367
|
end
|
56
368
|
|
57
|
-
it_behaves_like 'a
|
369
|
+
it_behaves_like 'a search supporting automatic as_indexed_json creation'
|
58
370
|
end
|
59
371
|
|
60
|
-
context 'with
|
372
|
+
context 'with articles and comments' do
|
373
|
+
before(:each) do
|
374
|
+
load 'setup/articles_with_comments.rb'
|
375
|
+
end
|
376
|
+
|
377
|
+
after(:each) do
|
378
|
+
ActiveRecord::Schema.define(:version => 2) do
|
379
|
+
drop_table :articles
|
380
|
+
drop_table :comments
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
it_behaves_like 'a search supporting automatic as_indexed_json creation'
|
385
|
+
it_behaves_like 'a search supporting association'
|
386
|
+
end
|
387
|
+
|
388
|
+
context 'with articles, comments and delayed_jobs' do
|
61
389
|
before(:each) do
|
62
390
|
load 'setup/articles_with_comments_and_delayed_jobs.rb'
|
63
391
|
end
|
@@ -70,6 +398,24 @@ RSpec.describe 'integration' do
|
|
70
398
|
end
|
71
399
|
end
|
72
400
|
|
73
|
-
it_behaves_like 'a
|
401
|
+
it_behaves_like 'a search supporting automatic as_indexed_json creation'
|
402
|
+
it_behaves_like 'a search supporting association'
|
403
|
+
end
|
404
|
+
|
405
|
+
context 'with authors, books and tags' do
|
406
|
+
before(:each) do
|
407
|
+
load 'setup/authors_and_books_with_tags.rb'
|
408
|
+
end
|
409
|
+
|
410
|
+
after(:each) do
|
411
|
+
ActiveRecord::Schema.define(:version => 2) do
|
412
|
+
drop_table :books
|
413
|
+
drop_table :tags
|
414
|
+
drop_table :author_profiles
|
415
|
+
drop_table :authors
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
it_behaves_like 'a search supporting polymorphic associations'
|
74
420
|
end
|
75
421
|
end
|
@@ -12,19 +12,49 @@ RSpec.describe Elasticsearch::Model::Extensions::PartialUpdating do
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
-
let(:as_json_options) {
|
16
|
-
described_class.build_as_json_options(klass: Article, props: Article.mappings.to_hash[Article.document_type.intern][:properties])
|
17
|
-
}
|
18
|
-
|
19
15
|
subject {
|
20
16
|
Article.last
|
21
17
|
}
|
22
18
|
|
23
19
|
specify {
|
24
|
-
expect(
|
20
|
+
expect(subject.respond_to? :partially_update_document).to be_truthy
|
25
21
|
}
|
26
22
|
|
27
23
|
specify {
|
28
|
-
expect(subject.
|
24
|
+
expect(subject.as_indexed_json).to eq(
|
25
|
+
"comments"=>[{"body"=>'Comment1'}],
|
26
|
+
"num_comments"=>1,
|
27
|
+
"title"=>'Coding',
|
28
|
+
"created_at"=>subject.created_at
|
29
|
+
)
|
29
30
|
}
|
31
|
+
|
32
|
+
describe Elasticsearch::Model::Extensions::PartialUpdating::PartialUpdater do
|
33
|
+
subject {
|
34
|
+
described_class.new(Article)
|
35
|
+
}
|
36
|
+
|
37
|
+
specify {
|
38
|
+
expect(subject.as_json_options).to include(methods: "num_comments")
|
39
|
+
}
|
40
|
+
|
41
|
+
specify {
|
42
|
+
expect(subject.build_partial_document_for_update(record: Article.last, changed_attributes: [:comments])).to include(:comments, :num_comments)
|
43
|
+
}
|
44
|
+
|
45
|
+
def partially_updating_a_record
|
46
|
+
article = Article.last
|
47
|
+
subject.update_document(id: article.id, doc: { comments: [ { body: 'ModifiedComment ' } ] })
|
48
|
+
Article.__elasticsearch__.refresh_index!
|
49
|
+
end
|
50
|
+
|
51
|
+
def number_of_articles_for_the_new_comment
|
52
|
+
Article.search('ModifiedComment').records.size
|
53
|
+
end
|
54
|
+
|
55
|
+
specify {
|
56
|
+
expect { partially_updating_a_record }.to change { number_of_articles_for_the_new_comment }.by(1)
|
57
|
+
}
|
58
|
+
end
|
30
59
|
end
|
60
|
+
|
@@ -26,12 +26,10 @@ class ::Article < ActiveRecord::Base
|
|
26
26
|
include Elasticsearch::Model::Extensions::BatchUpdating
|
27
27
|
include Elasticsearch::Model::Extensions::PartialUpdating
|
28
28
|
|
29
|
-
DEPENDENT_CUSTOM_ATTRIBUTES = {
|
30
|
-
%w| comments | => %w| num_comments |
|
31
|
-
}
|
32
|
-
|
33
29
|
include Elasticsearch::Model::Extensions::DependencyTracking
|
34
30
|
|
31
|
+
tracks_attributes_dependencies %w| comments | => %w| num_comments |
|
32
|
+
|
35
33
|
settings index: {number_of_shards: 1, number_of_replicas: 0} do
|
36
34
|
mapping do
|
37
35
|
indexes :title, type: 'string', analyzer: 'snowball'
|