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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -0
  3. data/Gemfile +2 -1
  4. data/README.md +5 -0
  5. data/gemfiles/rails41.gemfile +11 -0
  6. data/gemfiles/rails41.gemfile.lock +109 -0
  7. data/lib/elasticsearch/model/extensions/association_path_finding/association_path_finder.rb +19 -0
  8. data/lib/elasticsearch/model/extensions/association_path_finding/mapping_node.rb +68 -0
  9. data/lib/elasticsearch/model/extensions/association_path_finding/shortest_path.rb +108 -0
  10. data/lib/elasticsearch/model/extensions/batch_updating.rb +9 -52
  11. data/lib/elasticsearch/model/extensions/batch_updating/batch_updater.rb +68 -0
  12. data/lib/elasticsearch/model/extensions/configuration.rb +33 -5
  13. data/lib/elasticsearch/model/extensions/dependency_tracking.rb +17 -21
  14. data/lib/elasticsearch/model/extensions/dependency_tracking/dependency_tracker.rb +52 -0
  15. data/lib/elasticsearch/model/extensions/mapping_reflection.rb +7 -80
  16. data/lib/elasticsearch/model/extensions/mapping_reflection/mapping_reflector.rb +107 -0
  17. data/lib/elasticsearch/model/extensions/outer_document_updating.rb +0 -21
  18. data/lib/elasticsearch/model/extensions/partial_updating.rb +16 -116
  19. data/lib/elasticsearch/model/extensions/partial_updating/partial_updater.rb +149 -0
  20. data/lib/elasticsearch/model/extensions/proxy.rb +0 -0
  21. data/lib/elasticsearch/model/extensions/update_callback.rb +3 -2
  22. data/lib/elasticsearch/model/extensions/version.rb +1 -1
  23. data/spec/batch_updating/batch_updater_spec.rb +50 -0
  24. data/spec/batch_updating_spec.rb +134 -0
  25. data/spec/integration_spec.rb +350 -4
  26. data/spec/partial_updating_spec.rb +36 -6
  27. data/spec/setup/articles_with_comments.rb +2 -4
  28. data/spec/setup/articles_with_comments_and_delayed_jobs.rb +2 -4
  29. data/spec/setup/authors_and_books_with_tags.rb +183 -0
  30. data/spec/setup/items_and_categories.rb +0 -0
  31. data/spec/setup/sqlite.rb +1 -1
  32. data/spec/setup/undefine.rb +6 -6
  33. data/spec/spec_helper.rb +6 -0
  34. metadata +20 -4
  35. data/lib/elasticsearch/model/extensions/mapping_node.rb +0 -65
  36. data/lib/elasticsearch/model/extensions/shortest_path.rb +0 -106
@@ -1,6 +1,6 @@
1
1
  require 'elasticsearch/model/extensions/all'
2
2
 
3
- RSpec.shared_examples 'a normal elasticsearch-model object' do
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 normal elasticsearch-model object'
369
+ it_behaves_like 'a search supporting automatic as_indexed_json creation'
58
370
  end
59
371
 
60
- context 'with articles_with_comments_and_delayed_jobs' do
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 normal elasticsearch-model object'
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(as_json_options).to include(methods: :num_comments)
20
+ expect(subject.respond_to? :partially_update_document).to be_truthy
25
21
  }
26
22
 
27
23
  specify {
28
- expect(subject.build_partial_document_for_update(:comments)).to include(:comments, :num_comments)
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'