elasticsearch-model 0.1.9 → 7.1.1

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