elasticsearch-model 5.1.0 → 6.1.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 (125) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +5 -0
  3. data/README.md +18 -11
  4. data/Rakefile +27 -36
  5. data/elasticsearch-model.gemspec +5 -5
  6. data/examples/activerecord_custom_analyzer.rb +135 -0
  7. data/examples/activerecord_mapping_completion.rb +3 -18
  8. data/examples/datamapper_article.rb +11 -1
  9. data/gemfiles/3.0.gemfile +6 -1
  10. data/gemfiles/4.0.gemfile +7 -1
  11. data/gemfiles/5.0.gemfile +7 -1
  12. data/lib/elasticsearch/model/adapters/active_record.rb +7 -23
  13. data/lib/elasticsearch/model/adapters/mongoid.rb +10 -3
  14. data/lib/elasticsearch/model/importing.rb +1 -1
  15. data/lib/elasticsearch/model/indexing.rb +6 -4
  16. data/lib/elasticsearch/model/naming.rb +9 -2
  17. data/lib/elasticsearch/model/response/aggregations.rb +1 -1
  18. data/lib/elasticsearch/model/response/base.rb +3 -2
  19. data/lib/elasticsearch/model/response/pagination/kaminari.rb +109 -0
  20. data/lib/elasticsearch/model/response/pagination/will_paginate.rb +95 -0
  21. data/lib/elasticsearch/model/response/pagination.rb +2 -192
  22. data/lib/elasticsearch/model/response/result.rb +1 -1
  23. data/lib/elasticsearch/model/response/suggestions.rb +1 -1
  24. data/lib/elasticsearch/model/response.rb +11 -10
  25. data/lib/elasticsearch/model/version.rb +1 -1
  26. data/lib/elasticsearch/model.rb +15 -8
  27. data/spec/elasticsearch/model/adapter_spec.rb +119 -0
  28. data/spec/elasticsearch/model/adapters/active_record/associations_spec.rb +334 -0
  29. data/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +340 -0
  30. data/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb +18 -0
  31. data/spec/elasticsearch/model/adapters/active_record/import_spec.rb +187 -0
  32. data/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb +110 -0
  33. data/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb +38 -0
  34. data/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb +315 -0
  35. data/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb +75 -0
  36. data/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb +61 -0
  37. data/spec/elasticsearch/model/adapters/active_record_spec.rb +207 -0
  38. data/spec/elasticsearch/model/adapters/default_spec.rb +41 -0
  39. data/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb +267 -0
  40. data/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb +66 -0
  41. data/spec/elasticsearch/model/adapters/mongoid_spec.rb +235 -0
  42. data/spec/elasticsearch/model/adapters/multiple_spec.rb +125 -0
  43. data/spec/elasticsearch/model/callbacks_spec.rb +33 -0
  44. data/spec/elasticsearch/model/client_spec.rb +66 -0
  45. data/spec/elasticsearch/model/hash_wrapper_spec.rb +12 -0
  46. data/spec/elasticsearch/model/importing_spec.rb +214 -0
  47. data/spec/elasticsearch/model/indexing_spec.rb +918 -0
  48. data/spec/elasticsearch/model/module_spec.rb +101 -0
  49. data/spec/elasticsearch/model/multimodel_spec.rb +55 -0
  50. data/spec/elasticsearch/model/naming_inheritance_spec.rb +184 -0
  51. data/spec/elasticsearch/model/naming_spec.rb +186 -0
  52. data/spec/elasticsearch/model/proxy_spec.rb +107 -0
  53. data/spec/elasticsearch/model/response/aggregations_spec.rb +66 -0
  54. data/spec/elasticsearch/model/response/base_spec.rb +90 -0
  55. data/spec/elasticsearch/model/response/pagination/kaminari_spec.rb +410 -0
  56. data/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb +262 -0
  57. data/spec/elasticsearch/model/response/records_spec.rb +118 -0
  58. data/spec/elasticsearch/model/response/response_spec.rb +131 -0
  59. data/spec/elasticsearch/model/response/result_spec.rb +122 -0
  60. data/spec/elasticsearch/model/response/results_spec.rb +56 -0
  61. data/spec/elasticsearch/model/searching_search_request_spec.rb +112 -0
  62. data/spec/elasticsearch/model/searching_spec.rb +49 -0
  63. data/spec/elasticsearch/model/serializing_spec.rb +22 -0
  64. data/spec/spec_helper.rb +161 -0
  65. data/spec/support/app/answer.rb +33 -0
  66. data/spec/support/app/article.rb +22 -0
  67. data/spec/support/app/article_for_pagination.rb +12 -0
  68. data/spec/support/app/article_with_custom_serialization.rb +13 -0
  69. data/spec/support/app/article_with_dynamic_index_name.rb +15 -0
  70. data/spec/support/app/author.rb +9 -0
  71. data/spec/support/app/authorship.rb +4 -0
  72. data/spec/support/app/category.rb +3 -0
  73. data/spec/support/app/comment.rb +3 -0
  74. data/spec/support/app/episode.rb +11 -0
  75. data/spec/support/app/image.rb +19 -0
  76. data/spec/support/app/import_article.rb +12 -0
  77. data/spec/support/app/mongoid_article.rb +21 -0
  78. data/spec/support/app/namespaced_book.rb +10 -0
  79. data/spec/support/app/parent_and_child_searchable.rb +24 -0
  80. data/spec/support/app/post.rb +14 -0
  81. data/spec/support/app/question.rb +27 -0
  82. data/spec/support/app/searchable.rb +48 -0
  83. data/spec/support/app/series.rb +11 -0
  84. data/spec/support/app.rb +21 -0
  85. data/spec/support/model.json +1 -0
  86. metadata +134 -90
  87. data/test/integration/active_record_associations_parent_child_test.rb +0 -147
  88. data/test/integration/active_record_associations_test.rb +0 -339
  89. data/test/integration/active_record_basic_test.rb +0 -251
  90. data/test/integration/active_record_custom_serialization_test.rb +0 -67
  91. data/test/integration/active_record_import_test.rb +0 -115
  92. data/test/integration/active_record_namespaced_model_test.rb +0 -54
  93. data/test/integration/active_record_pagination_test.rb +0 -149
  94. data/test/integration/dynamic_index_name_test.rb +0 -52
  95. data/test/integration/mongoid_basic_test.rb +0 -176
  96. data/test/integration/multiple_models_test.rb +0 -176
  97. data/test/support/model.json +0 -1
  98. data/test/test_helper.rb +0 -92
  99. data/test/unit/adapter_active_record_test.rb +0 -157
  100. data/test/unit/adapter_default_test.rb +0 -41
  101. data/test/unit/adapter_mongoid_test.rb +0 -104
  102. data/test/unit/adapter_multiple_test.rb +0 -106
  103. data/test/unit/adapter_test.rb +0 -69
  104. data/test/unit/callbacks_test.rb +0 -31
  105. data/test/unit/client_test.rb +0 -27
  106. data/test/unit/hash_wrapper_test.rb +0 -13
  107. data/test/unit/importing_test.rb +0 -203
  108. data/test/unit/indexing_test.rb +0 -687
  109. data/test/unit/module_test.rb +0 -68
  110. data/test/unit/multimodel_test.rb +0 -38
  111. data/test/unit/naming_inheritance_test.rb +0 -94
  112. data/test/unit/naming_test.rb +0 -103
  113. data/test/unit/proxy_test.rb +0 -98
  114. data/test/unit/response_aggregations_test.rb +0 -46
  115. data/test/unit/response_base_test.rb +0 -40
  116. data/test/unit/response_pagination_kaminari_test.rb +0 -433
  117. data/test/unit/response_pagination_will_paginate_test.rb +0 -398
  118. data/test/unit/response_records_test.rb +0 -91
  119. data/test/unit/response_result_test.rb +0 -90
  120. data/test/unit/response_results_test.rb +0 -31
  121. data/test/unit/response_test.rb +0 -104
  122. data/test/unit/searching_search_request_test.rb +0 -78
  123. data/test/unit/searching_test.rb +0 -41
  124. data/test/unit/serializing_test.rb +0 -17
  125. /data/{test → spec}/support/model.yml +0 -0
@@ -0,0 +1,918 @@
1
+ require 'spec_helper'
2
+
3
+ describe Elasticsearch::Model::Indexing do
4
+
5
+ before(:all) do
6
+ class ::DummyIndexingModel
7
+ extend ActiveModel::Naming
8
+ extend Elasticsearch::Model::Naming::ClassMethods
9
+ extend Elasticsearch::Model::Indexing::ClassMethods
10
+
11
+ def self.foo
12
+ 'bar'
13
+ end
14
+ end
15
+
16
+ class NotFound < Exception; end
17
+ end
18
+
19
+ after(:all) do
20
+ remove_classes(DummyIndexingModel, NotFound)
21
+ end
22
+
23
+ describe 'the Settings class' do
24
+
25
+ it 'should be convertible to a hash' do
26
+ expect(Elasticsearch::Model::Indexing::Settings.new(foo: 'bar').to_hash).to eq(foo: 'bar')
27
+ end
28
+
29
+ it 'should be convertible to json' do
30
+ expect(Elasticsearch::Model::Indexing::Settings.new(foo: 'bar').as_json).to eq(foo: 'bar')
31
+ end
32
+ end
33
+
34
+ describe '#settings' do
35
+
36
+ it 'returns an instance of the Settings class' do
37
+ expect(DummyIndexingModel.settings).to be_a(Elasticsearch::Model::Indexing::Settings)
38
+ end
39
+
40
+ context 'when the settings are updated' do
41
+
42
+ before do
43
+ DummyIndexingModel.settings(foo: 'boo')
44
+ DummyIndexingModel.settings(bar: 'bam')
45
+ end
46
+
47
+ it 'updates the settings on the class' do
48
+ expect(DummyIndexingModel.settings.to_hash).to eq(foo: 'boo', bar: 'bam')
49
+ end
50
+ end
51
+
52
+ context 'when the settings are updated with a yml file' do
53
+
54
+ before do
55
+ DummyIndexingModel.settings File.open('spec/support/model.yml')
56
+ DummyIndexingModel.settings bar: 'bam'
57
+ end
58
+
59
+ it 'updates the settings on the class' do
60
+ expect(DummyIndexingModel.settings.to_hash).to eq(foo: 'boo', bar: 'bam', 'baz' => 'qux')
61
+ end
62
+ end
63
+
64
+ context 'when the settings are updated with a json file' do
65
+
66
+ before do
67
+ DummyIndexingModel.settings File.open('spec/support/model.json')
68
+ DummyIndexingModel.settings bar: 'bam'
69
+ end
70
+
71
+ it 'updates the settings on the class' do
72
+ expect(DummyIndexingModel.settings.to_hash).to eq(foo: 'boo', bar: 'bam', 'baz' => 'qux', 'laz' => 'qux')
73
+ end
74
+ end
75
+ end
76
+
77
+ describe '#mappings' do
78
+
79
+ let(:expected_mapping_hash) do
80
+ { :mytype => { foo: 'bar', :properties => {} } }
81
+ end
82
+
83
+ it 'returns an instance of the Mappings class' do
84
+ expect(DummyIndexingModel.mappings).to be_a(Elasticsearch::Model::Indexing::Mappings)
85
+ end
86
+
87
+ it 'raises an exception when there is no type passed to the #initialize method' do
88
+ expect {
89
+ Elasticsearch::Model::Indexing::Mappings.new
90
+ }.to raise_exception(ArgumentError)
91
+ end
92
+
93
+ it 'should be convertible to a hash' do
94
+ expect(Elasticsearch::Model::Indexing::Mappings.new(:mytype, { foo: 'bar' }).to_hash).to eq(expected_mapping_hash)
95
+ end
96
+
97
+ it 'should be convertible to json' do
98
+ expect(Elasticsearch::Model::Indexing::Mappings.new(:mytype, { foo: 'bar' }).as_json).to eq(expected_mapping_hash)
99
+ end
100
+
101
+ context 'when specific mappings are defined' do
102
+
103
+ let(:mappings) do
104
+ Elasticsearch::Model::Indexing::Mappings.new(:mytype)
105
+ end
106
+
107
+ before do
108
+ mappings.indexes :foo, { type: 'boolean', include_in_all: false }
109
+ mappings.indexes :bar
110
+ end
111
+
112
+ it 'creates the correct mapping definition' do
113
+ expect(mappings.to_hash[:mytype][:properties][:foo][:type]).to eq('boolean')
114
+ end
115
+
116
+ it 'uses text as the default type' do
117
+ expect(mappings.to_hash[:mytype][:properties][:bar][:type]).to eq('text')
118
+ end
119
+
120
+ context 'when mappings are defined for multiple fields' do
121
+
122
+ before do
123
+ mappings.indexes :my_field, type: 'text' do
124
+ indexes :raw, type: 'keyword'
125
+ end
126
+ end
127
+
128
+ it 'defines the mapping for all the fields' do
129
+ expect(mappings.to_hash[:mytype][:properties][:my_field][:type]).to eq('text')
130
+ expect(mappings.to_hash[:mytype][:properties][:my_field][:fields][:raw][:type]).to eq('keyword')
131
+ expect(mappings.to_hash[:mytype][:properties][:my_field][:fields][:raw][:properties]).to be_nil
132
+ end
133
+ end
134
+
135
+ context 'when embedded properties are defined' do
136
+
137
+ before do
138
+ mappings.indexes :foo do
139
+ indexes :bar
140
+ end
141
+
142
+ mappings.indexes :foo_object, type: 'object' do
143
+ indexes :bar
144
+ end
145
+
146
+ mappings.indexes :foo_nested, type: 'nested' do
147
+ indexes :bar
148
+ end
149
+
150
+ mappings.indexes :foo_nested_as_symbol, type: :nested do
151
+ indexes :bar
152
+ end
153
+ end
154
+
155
+ it 'defines mappings for the embedded properties' do
156
+ expect(mappings.to_hash[:mytype][:properties][:foo][:type]).to eq('object')
157
+ expect(mappings.to_hash[:mytype][:properties][:foo][:properties][:bar][:type]).to eq('text')
158
+ expect(mappings.to_hash[:mytype][:properties][:foo][:fields]).to be_nil
159
+
160
+ expect(mappings.to_hash[:mytype][:properties][:foo_object][:type]).to eq('object')
161
+ expect(mappings.to_hash[:mytype][:properties][:foo_object][:properties][:bar][:type]).to eq('text')
162
+ expect(mappings.to_hash[:mytype][:properties][:foo_object][:fields]).to be_nil
163
+
164
+ expect(mappings.to_hash[:mytype][:properties][:foo_nested][:type]).to eq('nested')
165
+ expect(mappings.to_hash[:mytype][:properties][:foo_nested][:properties][:bar][:type]).to eq('text')
166
+ expect(mappings.to_hash[:mytype][:properties][:foo_nested][:fields]).to be_nil
167
+
168
+ expect(mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:type]).to eq(:nested)
169
+ expect(mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:properties]).not_to be_nil
170
+ expect(mappings.to_hash[:mytype][:properties][:foo_nested_as_symbol][:fields]).to be_nil
171
+ end
172
+ end
173
+ end
174
+
175
+ context 'when the method is called on a class' do
176
+
177
+ before do
178
+ DummyIndexingModel.mappings(foo: 'boo')
179
+ DummyIndexingModel.mappings(bar: 'bam')
180
+ end
181
+
182
+ let(:expected_mappings_hash) do
183
+ { _doc: { foo: "boo", bar: "bam", properties: {} } }
184
+ end
185
+
186
+ it 'sets the mappings' do
187
+ expect(DummyIndexingModel.mappings.to_hash).to eq(expected_mappings_hash)
188
+ end
189
+
190
+ context 'when the method is called with a block' do
191
+
192
+ before do
193
+ DummyIndexingModel.mapping do
194
+ indexes :foo, type: 'boolean'
195
+ end
196
+ end
197
+
198
+ it 'sets the mappings' do
199
+ expect(DummyIndexingModel.mapping.to_hash[:_doc][:properties][:foo][:type]).to eq('boolean')
200
+ end
201
+ end
202
+ end
203
+ end
204
+
205
+ describe 'instance methods' do
206
+
207
+ before(:all) do
208
+ class ::DummyIndexingModelWithCallbacks
209
+ extend Elasticsearch::Model::Indexing::ClassMethods
210
+ include Elasticsearch::Model::Indexing::InstanceMethods
211
+
212
+ def self.before_save(&block)
213
+ (@callbacks ||= {})[block.hash] = block
214
+ end
215
+
216
+ def changes_to_save
217
+ {:foo => ['One', 'Two']}
218
+ end
219
+ end
220
+
221
+ class ::DummyIndexingModelWithNoChanges
222
+ extend Elasticsearch::Model::Indexing::ClassMethods
223
+ include Elasticsearch::Model::Indexing::InstanceMethods
224
+
225
+ def self.before_save(&block)
226
+ (@callbacks ||= {})[block.hash] = block
227
+ end
228
+
229
+ def changes_to_save
230
+ {}
231
+ end
232
+ end
233
+
234
+ class ::DummyIndexingModelWithCallbacksAndCustomAsIndexedJson
235
+ extend Elasticsearch::Model::Indexing::ClassMethods
236
+ include Elasticsearch::Model::Indexing::InstanceMethods
237
+
238
+ def self.before_save(&block)
239
+ (@callbacks ||= {})[block.hash] = block
240
+ end
241
+
242
+ def changes_to_save
243
+ {:foo => ['A', 'B'], :bar => ['C', 'D']}
244
+ end
245
+
246
+ def as_indexed_json(options={})
247
+ { :foo => 'B' }
248
+ end
249
+ end
250
+
251
+ class ::DummyIndexingModelWithOldDirty
252
+ extend Elasticsearch::Model::Indexing::ClassMethods
253
+ include Elasticsearch::Model::Indexing::InstanceMethods
254
+
255
+ def self.before_save(&block)
256
+ (@callbacks ||= {})[block.hash] = block
257
+ end
258
+
259
+ def changes
260
+ {:foo => ['One', 'Two']}
261
+ end
262
+ end
263
+ end
264
+
265
+ after(:all) do
266
+ Object.send(:remove_const, :DummyIndexingModelWithCallbacks) if defined?(DummyIndexingModelWithCallbacks)
267
+ Object.send(:remove_const, :DummyIndexingModelWithNoChanges) if defined?(DummyIndexingModelWithNoChanges)
268
+ Object.send(:remove_const, :DummyIndexingModelWithCallbacksAndCustomAsIndexedJson) if defined?(DummyIndexingModelWithCallbacksAndCustomAsIndexedJson)
269
+ Object.send(:remove_const, :DummyIndexingModelWithOldDirty) if defined?(DummyIndexingModelWithOldDirty)
270
+ end
271
+
272
+ context 'when the module is included' do
273
+
274
+ context 'when the model uses the old ActiveModel::Dirty' do
275
+
276
+ before do
277
+ DummyIndexingModelWithOldDirty.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods
278
+ end
279
+
280
+ it 'registers callbacks' do
281
+ expect(DummyIndexingModelWithOldDirty.instance_variable_get(:@callbacks)).not_to be_empty
282
+ end
283
+
284
+ let(:instance) do
285
+ DummyIndexingModelWithOldDirty.new
286
+ end
287
+
288
+ it 'sets the @__changed_model_attributes variable before the callback' do
289
+ DummyIndexingModelWithOldDirty.instance_variable_get(:@callbacks).each do |n, callback|
290
+ instance.instance_eval(&callback)
291
+ expect(instance.instance_variable_get(:@__changed_model_attributes)).to eq(foo: 'Two')
292
+ end
293
+ end
294
+ end
295
+
296
+ context 'when the model users the current ActiveModel::Dirty' do
297
+
298
+ before do
299
+ DummyIndexingModelWithCallbacks.__send__ :include, Elasticsearch::Model::Indexing::InstanceMethods
300
+ end
301
+
302
+ it 'registers callbacks' do
303
+ expect(DummyIndexingModelWithCallbacks.instance_variable_get(:@callbacks)).not_to be_empty
304
+ end
305
+
306
+ let(:instance) do
307
+ DummyIndexingModelWithCallbacks.new
308
+ end
309
+
310
+ it 'sets the @__changed_model_attributes variable before the callback' do
311
+ DummyIndexingModelWithCallbacks.instance_variable_get(:@callbacks).each do |n, callback|
312
+ instance.instance_eval(&callback)
313
+ expect(instance.instance_variable_get(:@__changed_model_attributes)).to eq(foo: 'Two')
314
+ end
315
+ end
316
+ end
317
+ end
318
+
319
+ describe '#index_document' do
320
+
321
+ before do
322
+ expect(instance).to receive(:client).and_return(client)
323
+ expect(instance).to receive(:as_indexed_json).and_return('JSON')
324
+ expect(instance).to receive(:index_name).and_return('foo')
325
+ expect(instance).to receive(:document_type).and_return('bar')
326
+ expect(instance).to receive(:id).and_return('1')
327
+ end
328
+
329
+ let(:client) do
330
+ double('client')
331
+ end
332
+
333
+ let(:instance) do
334
+ DummyIndexingModelWithCallbacks.new
335
+ end
336
+
337
+ context 'when no options are passed to the method' do
338
+
339
+ before do
340
+ expect(client).to receive(:index).with(index: 'foo', type: 'bar', id: '1', body: 'JSON').and_return(true)
341
+ end
342
+
343
+ it 'provides the method on an instance' do
344
+ expect(instance.index_document).to be(true)
345
+ end
346
+ end
347
+
348
+ context 'when extra options are passed to the method' do
349
+
350
+ before do
351
+ expect(client).to receive(:index).with(index: 'foo', type: 'bar', id: '1', body: 'JSON', parent: 'A').and_return(true)
352
+ end
353
+
354
+ it 'passes the extra options to the method call on the client' do
355
+ expect(instance.index_document(parent: 'A')).to be(true)
356
+ end
357
+ end
358
+ end
359
+
360
+ describe '#delete_document' do
361
+
362
+ before do
363
+ expect(instance).to receive(:client).and_return(client)
364
+ expect(instance).to receive(:index_name).and_return('foo')
365
+ expect(instance).to receive(:document_type).and_return('bar')
366
+ expect(instance).to receive(:id).and_return('1')
367
+ end
368
+
369
+ let(:client) do
370
+ double('client')
371
+ end
372
+
373
+ let(:instance) do
374
+ DummyIndexingModelWithCallbacks.new
375
+ end
376
+
377
+ context 'when no options are passed to the method' do
378
+
379
+ before do
380
+ expect(client).to receive(:delete).with(index: 'foo', type: 'bar', id: '1').and_return(true)
381
+ end
382
+
383
+ it 'provides the method on an instance' do
384
+ expect(instance.delete_document).to be(true)
385
+ end
386
+ end
387
+
388
+ context 'when extra options are passed to the method' do
389
+
390
+ before do
391
+ expect(client).to receive(:delete).with(index: 'foo', type: 'bar', id: '1', parent: 'A').and_return(true)
392
+ end
393
+
394
+ it 'passes the extra options to the method call on the client' do
395
+ expect(instance.delete_document(parent: 'A')).to be(true)
396
+ end
397
+ end
398
+ end
399
+
400
+ describe '#update_document' do
401
+
402
+ let(:client) do
403
+ double('client')
404
+ end
405
+
406
+ let(:instance) do
407
+ DummyIndexingModelWithCallbacks.new
408
+ end
409
+
410
+ context 'when no changes are present' do
411
+
412
+ before do
413
+ expect(instance).to receive(:index_document).and_return(true)
414
+ expect(client).to receive(:update).never
415
+ instance.instance_variable_set(:@__changed_model_attributes, nil)
416
+ end
417
+
418
+ it 'updates the document' do
419
+ expect(instance.update_document).to be(true)
420
+ end
421
+ end
422
+
423
+ context 'when changes are present' do
424
+
425
+ before do
426
+ allow(instance).to receive(:client).and_return(client)
427
+ allow(instance).to receive(:index_name).and_return('foo')
428
+ allow(instance).to receive(:document_type).and_return('bar')
429
+ allow(instance).to receive(:id).and_return('1')
430
+ end
431
+
432
+ context 'when the changes are included in the as_indexed_json representation' do
433
+
434
+ before do
435
+ instance.instance_variable_set(:@__changed_model_attributes, { foo: 'bar' })
436
+ expect(client).to receive(:update).with(index: 'foo', type: 'bar', id: '1', body: { doc: { foo: 'bar' } }).and_return(true)
437
+ end
438
+
439
+ it 'updates the document' do
440
+ expect(instance.update_document).to be(true)
441
+ end
442
+ end
443
+
444
+ context 'when the changes are not all included in the as_indexed_json representation' do
445
+
446
+ let(:instance) do
447
+ DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new
448
+ end
449
+
450
+ before do
451
+ instance.instance_variable_set(:@__changed_model_attributes, {'foo' => 'B', 'bar' => 'D' })
452
+ expect(client).to receive(:update).with(index: 'foo', type: 'bar', id: '1', body: { doc: { foo: 'B' } }).and_return(true)
453
+ end
454
+
455
+ it 'updates the document' do
456
+ expect(instance.update_document).to be(true)
457
+ end
458
+ end
459
+
460
+ context 'when none of the changes are included in the as_indexed_json representation' do
461
+
462
+ let(:instance) do
463
+ DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new
464
+ end
465
+
466
+ before do
467
+ instance.instance_variable_set(:@__changed_model_attributes, {'bar' => 'D' })
468
+ end
469
+
470
+ it 'does not update the document' do
471
+ expect(instance.update_document).to_not be(true)
472
+ end
473
+ end
474
+
475
+ context 'when there are partial updates' do
476
+
477
+ let(:instance) do
478
+ DummyIndexingModelWithCallbacksAndCustomAsIndexedJson.new
479
+ end
480
+
481
+ before do
482
+ instance.instance_variable_set(:@__changed_model_attributes, { 'foo' => { 'bar' => 'BAR'} })
483
+ expect(instance).to receive(:as_indexed_json).and_return('foo' => 'BAR')
484
+ expect(client).to receive(:update).with(index: 'foo', type: 'bar', id: '1', body: { doc: { 'foo' => 'BAR' } }).and_return(true)
485
+ end
486
+
487
+ it 'updates the document' do
488
+ expect(instance.update_document).to be(true)
489
+ end
490
+ end
491
+ end
492
+ end
493
+
494
+ describe '#update_document_attributes' do
495
+
496
+ let(:client) do
497
+ double('client')
498
+ end
499
+
500
+ let(:instance) do
501
+ DummyIndexingModelWithCallbacks.new
502
+ end
503
+
504
+ context 'when changes are present' do
505
+
506
+ before do
507
+ expect(instance).to receive(:client).and_return(client)
508
+ expect(instance).to receive(:index_name).and_return('foo')
509
+ expect(instance).to receive(:document_type).and_return('bar')
510
+ expect(instance).to receive(:id).and_return('1')
511
+ instance.instance_variable_set(:@__changed_model_attributes, { author: 'john' })
512
+ end
513
+
514
+ context 'when no options are specified' do
515
+
516
+ before do
517
+ expect(client).to receive(:update).with(index: 'foo', type: 'bar', id: '1', body: { doc: { title: 'green' } }).and_return(true)
518
+ end
519
+
520
+ it 'updates the document' do
521
+ expect(instance.update_document_attributes(title: 'green')).to be(true)
522
+ end
523
+ end
524
+
525
+ context 'when extra options are provided' do
526
+
527
+ before do
528
+ expect(client).to receive(:update).with(index: 'foo', type: 'bar', id: '1', body: { doc: { title: 'green' } }, refresh: true).and_return(true)
529
+ end
530
+
531
+ it 'updates the document' do
532
+ expect(instance.update_document_attributes({ title: 'green' }, refresh: true)).to be(true)
533
+ end
534
+ end
535
+ end
536
+ end
537
+ end
538
+
539
+ describe '#index_exists?' do
540
+
541
+ before do
542
+ expect(DummyIndexingModel).to receive(:client).and_return(client)
543
+ end
544
+
545
+ context 'when the index exists' do
546
+
547
+ let(:client) do
548
+ double('client', indices: double('indices', exists: true))
549
+ end
550
+
551
+ it 'returns true' do
552
+ expect(DummyIndexingModel.index_exists?).to be(true)
553
+ end
554
+ end
555
+
556
+ context 'when the index does not exists' do
557
+
558
+ let(:client) do
559
+ double('client', indices: double('indices', exists: false))
560
+ end
561
+
562
+ it 'returns false' do
563
+ expect(DummyIndexingModel.index_exists?).to be(false)
564
+ end
565
+ end
566
+
567
+ context 'when the index API raises an error' do
568
+
569
+ let(:client) do
570
+ double('client').tap do |cl|
571
+ expect(cl).to receive(:indices).and_raise(StandardError)
572
+ end
573
+ end
574
+
575
+ it 'returns false' do
576
+ expect(DummyIndexingModel.index_exists?).to be(false)
577
+ end
578
+ end
579
+
580
+ context 'when the indices.exists API raises an error' do
581
+
582
+ let(:client) do
583
+ double('client', indices: indices)
584
+ end
585
+
586
+ let(:indices) do
587
+ double('indices').tap do |ind|
588
+ expect(ind).to receive(:exists).and_raise(StandardError)
589
+ end
590
+ end
591
+
592
+ it 'returns false' do
593
+ expect(DummyIndexingModel.index_exists?).to be(false)
594
+ end
595
+ end
596
+ end
597
+
598
+ describe '#delete_index!' do
599
+
600
+ before(:all) do
601
+ class ::DummyIndexingModelForRecreate
602
+ extend ActiveModel::Naming
603
+ extend Elasticsearch::Model::Naming::ClassMethods
604
+ extend Elasticsearch::Model::Indexing::ClassMethods
605
+ end
606
+ end
607
+
608
+ after(:all) do
609
+ Object.send(:remove_const, :DummyIndexingModelForRecreate) if defined?(DummyIndexingModelForRecreate)
610
+ end
611
+
612
+ context 'when the index is not found' do
613
+
614
+ let(:client) do
615
+ double('client', indices: indices, transport: double('transport', { logger: nil }))
616
+ end
617
+
618
+ let(:indices) do
619
+ double('indices').tap do |ind|
620
+ expect(ind).to receive(:delete).and_raise(NotFound)
621
+ end
622
+ end
623
+
624
+ before do
625
+ expect(DummyIndexingModelForRecreate).to receive(:client).at_most(3).times.and_return(client)
626
+ end
627
+
628
+ context 'when the force option is true' do
629
+
630
+ it 'deletes the index without raising an exception' do
631
+ expect(DummyIndexingModelForRecreate.delete_index!(force: true)).to be_nil
632
+ end
633
+
634
+ context 'when the client has a logger' do
635
+
636
+ let(:logger) do
637
+ Logger.new(STDOUT).tap { |l| l.level = Logger::DEBUG }
638
+ end
639
+
640
+ let(:client) do
641
+ double('client', indices: indices, transport: double('transport', { logger: logger }))
642
+ end
643
+
644
+ it 'deletes the index without raising an exception' do
645
+ expect(DummyIndexingModelForRecreate.delete_index!(force: true)).to be_nil
646
+ end
647
+
648
+ it 'logs the message that the index is not found' do
649
+ expect(logger).to receive(:debug)
650
+ expect(DummyIndexingModelForRecreate.delete_index!(force: true)).to be_nil
651
+ end
652
+ end
653
+ end
654
+
655
+ context 'when the force option is not provided' do
656
+
657
+ it 'raises an exception' do
658
+ expect {
659
+ DummyIndexingModelForRecreate.delete_index!
660
+ }.to raise_exception(NotFound)
661
+ end
662
+ end
663
+
664
+ context 'when the exception is not NotFound' do
665
+
666
+ let(:indices) do
667
+ double('indices').tap do |ind|
668
+ expect(ind).to receive(:delete).and_raise(Exception)
669
+ end
670
+ end
671
+
672
+ it 'raises an exception' do
673
+ expect {
674
+ DummyIndexingModelForRecreate.delete_index!
675
+ }.to raise_exception(Exception)
676
+ end
677
+ end
678
+ end
679
+
680
+ context 'when an index name is provided in the options' do
681
+
682
+ before do
683
+ expect(DummyIndexingModelForRecreate).to receive(:client).and_return(client)
684
+ expect(indices).to receive(:delete).with(index: 'custom-foo')
685
+ end
686
+
687
+ let(:client) do
688
+ double('client', indices: indices)
689
+ end
690
+
691
+ let(:indices) do
692
+ double('indices', delete: true)
693
+ end
694
+
695
+ it 'uses the index name' do
696
+ expect(DummyIndexingModelForRecreate.delete_index!(index: 'custom-foo'))
697
+ end
698
+ end
699
+ end
700
+
701
+ describe '#create_index' do
702
+
703
+ before(:all) do
704
+ class ::DummyIndexingModelForCreate
705
+ extend ActiveModel::Naming
706
+ extend Elasticsearch::Model::Naming::ClassMethods
707
+ extend Elasticsearch::Model::Indexing::ClassMethods
708
+
709
+ index_name 'foo'
710
+
711
+ settings index: { number_of_shards: 1 } do
712
+ mappings do
713
+ indexes :foo, analyzer: 'keyword'
714
+ end
715
+ end
716
+ end
717
+ end
718
+
719
+ after(:all) do
720
+ Object.send(:remove_const, :DummyIndexingModelForCreate) if defined?(DummyIndexingModelForCreate)
721
+ end
722
+
723
+ let(:client) do
724
+ double('client', indices: indices)
725
+ end
726
+
727
+ let(:indices) do
728
+ double('indices')
729
+ end
730
+
731
+ context 'when the index does not exist' do
732
+
733
+ before do
734
+ expect(DummyIndexingModelForCreate).to receive(:client).and_return(client)
735
+ expect(DummyIndexingModelForCreate).to receive(:index_exists?).and_return(false)
736
+ end
737
+
738
+ context 'when options are not provided' do
739
+
740
+ let(:expected_body) do
741
+ { mappings: { _doc: { properties: { foo: { analyzer: 'keyword',
742
+ type: 'text' } } } },
743
+ settings: { index: { number_of_shards: 1 } } }
744
+ end
745
+
746
+ before do
747
+ expect(indices).to receive(:create).with(index: 'foo', body: expected_body).and_return(true)
748
+ end
749
+
750
+ it 'creates the index' do
751
+ expect(DummyIndexingModelForCreate.create_index!).to be(true)
752
+ end
753
+ end
754
+
755
+ context 'when options are provided' do
756
+
757
+ let(:expected_body) do
758
+ { mappings: { foobar: { properties: { foo: { analyzer: 'bar' } } } },
759
+ settings: { index: { number_of_shards: 3 } } }
760
+ end
761
+
762
+ before do
763
+ expect(indices).to receive(:create).with(index: 'foobar', body: expected_body).and_return(true)
764
+ end
765
+
766
+ it 'creates the index' do
767
+ expect(DummyIndexingModelForCreate.create_index! \
768
+ index: 'foobar',
769
+ settings: { index: { number_of_shards: 3 } },
770
+ mappings: { foobar: { properties: { foo: { analyzer: 'bar' } } } }
771
+ ).to be(true)
772
+ end
773
+ end
774
+ end
775
+
776
+ context 'when the index exists' do
777
+
778
+ before do
779
+ expect(DummyIndexingModelForCreate).to receive(:index_exists?).and_return(true)
780
+ expect(indices).to receive(:create).never
781
+ end
782
+
783
+ it 'does not create the index' do
784
+ expect(DummyIndexingModelForCreate.create_index!).to be_nil
785
+ end
786
+ end
787
+
788
+ context 'when creating the index raises an exception' do
789
+
790
+ before do
791
+ expect(DummyIndexingModelForCreate).to receive(:client).and_return(client)
792
+ expect(DummyIndexingModelForCreate).to receive(:index_exists?).and_return(false)
793
+ expect(DummyIndexingModelForCreate).to receive(:delete_index!).and_return(true)
794
+ expect(indices).to receive(:create).and_raise(Exception)
795
+ end
796
+
797
+ it 'raises the exception' do
798
+ expect {
799
+ DummyIndexingModelForCreate.create_index!(force: true)
800
+ }.to raise_exception(Exception)
801
+ end
802
+ end
803
+
804
+ context 'when an index name is provided in the options' do
805
+
806
+ before do
807
+ expect(DummyIndexingModelForCreate).to receive(:client).and_return(client).twice
808
+ expect(indices).to receive(:exists).and_return(false)
809
+ expect(indices).to receive(:create).with(index: 'custom-foo', body: expected_body)
810
+ end
811
+
812
+ let(:expected_body) do
813
+ { mappings: { _doc: { properties: { foo: { analyzer: 'keyword',
814
+ type: 'text' } } } },
815
+ settings: { index: { number_of_shards: 1 } } }
816
+ end
817
+
818
+ it 'uses the index name' do
819
+ expect(DummyIndexingModelForCreate.create_index!(index: 'custom-foo'))
820
+ end
821
+ end
822
+
823
+ context 'when the logging level is debug'
824
+ end
825
+
826
+ describe '#refresh_index!' do
827
+
828
+ before(:all) do
829
+ class ::DummyIndexingModelForRefresh
830
+ extend ActiveModel::Naming
831
+ extend Elasticsearch::Model::Naming::ClassMethods
832
+ extend Elasticsearch::Model::Indexing::ClassMethods
833
+
834
+ index_name 'foo'
835
+
836
+ settings index: { number_of_shards: 1 } do
837
+ mappings do
838
+ indexes :foo, analyzer: 'keyword'
839
+ end
840
+ end
841
+ end
842
+ end
843
+
844
+ after(:all) do
845
+ Object.send(:remove_const, :DummyIndexingModelForRefresh) if defined?(DummyIndexingModelForRefresh)
846
+ end
847
+
848
+ let(:client) do
849
+ double('client', indices: indices, transport: double('transport', { logger: nil }))
850
+ end
851
+
852
+ let(:indices) do
853
+ double('indices')
854
+ end
855
+
856
+ before do
857
+ expect(DummyIndexingModelForRefresh).to receive(:client).at_most(3).times.and_return(client)
858
+ end
859
+
860
+ context 'when the force option is true' do
861
+
862
+ context 'when the operation raises a NotFound exception' do
863
+
864
+ before do
865
+ expect(indices).to receive(:refresh).and_raise(NotFound)
866
+ end
867
+
868
+ it 'does not raise an exception' do
869
+ expect(DummyIndexingModelForRefresh.refresh_index!(force: true)).to be_nil
870
+ end
871
+
872
+ context 'when the client has a logger' do
873
+
874
+ let(:logger) do
875
+ Logger.new(STDOUT).tap { |l| l.level = Logger::DEBUG }
876
+ end
877
+
878
+ let(:client) do
879
+ double('client', indices: indices, transport: double('transport', { logger: logger }))
880
+ end
881
+
882
+ it 'does not raise an exception' do
883
+ expect(DummyIndexingModelForRefresh.refresh_index!(force: true)).to be_nil
884
+ end
885
+
886
+ it 'logs the message that the index is not found' do
887
+ expect(logger).to receive(:debug)
888
+ expect(DummyIndexingModelForRefresh.refresh_index!(force: true)).to be_nil
889
+ end
890
+ end
891
+ end
892
+
893
+ context 'when the operation raises another type of exception' do
894
+
895
+ before do
896
+ expect(indices).to receive(:refresh).and_raise(Exception)
897
+ end
898
+
899
+ it 'does not raise an exception' do
900
+ expect {
901
+ DummyIndexingModelForRefresh.refresh_index!(force: true)
902
+ }.to raise_exception(Exception)
903
+ end
904
+ end
905
+ end
906
+
907
+ context 'when an index name is provided in the options' do
908
+
909
+ before do
910
+ expect(indices).to receive(:refresh).with(index: 'custom-foo')
911
+ end
912
+
913
+ it 'uses the index name' do
914
+ expect(DummyIndexingModelForRefresh.refresh_index!(index: 'custom-foo'))
915
+ end
916
+ end
917
+ end
918
+ end