elasticsearch-model-extensions 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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'