elasticsearch-model 6.0.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.
- checksums.yaml +4 -4
- data/Gemfile +5 -0
- data/README.md +14 -7
- data/Rakefile +27 -36
- data/elasticsearch-model.gemspec +1 -1
- data/examples/activerecord_mapping_completion.rb +2 -15
- data/gemfiles/3.0.gemfile +6 -1
- data/gemfiles/4.0.gemfile +7 -1
- data/gemfiles/5.0.gemfile +6 -0
- data/lib/elasticsearch/model.rb +15 -8
- data/lib/elasticsearch/model/adapters/active_record.rb +7 -26
- data/lib/elasticsearch/model/indexing.rb +5 -3
- data/lib/elasticsearch/model/naming.rb +6 -1
- data/lib/elasticsearch/model/response.rb +2 -2
- data/lib/elasticsearch/model/response/pagination.rb +2 -192
- data/lib/elasticsearch/model/response/pagination/kaminari.rb +109 -0
- data/lib/elasticsearch/model/response/pagination/will_paginate.rb +95 -0
- data/lib/elasticsearch/model/response/result.rb +1 -1
- data/lib/elasticsearch/model/version.rb +1 -1
- data/spec/elasticsearch/model/adapter_spec.rb +119 -0
- data/spec/elasticsearch/model/adapters/active_record/associations_spec.rb +334 -0
- data/spec/elasticsearch/model/adapters/active_record/basic_spec.rb +340 -0
- data/spec/elasticsearch/model/adapters/active_record/dynamic_index_name_spec.rb +18 -0
- data/spec/elasticsearch/model/adapters/active_record/import_spec.rb +187 -0
- data/spec/elasticsearch/model/adapters/active_record/multi_model_spec.rb +110 -0
- data/spec/elasticsearch/model/adapters/active_record/namespaced_model_spec.rb +38 -0
- data/spec/elasticsearch/model/adapters/active_record/pagination_spec.rb +315 -0
- data/spec/elasticsearch/model/adapters/active_record/parent_child_spec.rb +75 -0
- data/spec/elasticsearch/model/adapters/active_record/serialization_spec.rb +61 -0
- data/spec/elasticsearch/model/adapters/active_record_spec.rb +207 -0
- data/spec/elasticsearch/model/adapters/default_spec.rb +41 -0
- data/spec/elasticsearch/model/adapters/mongoid/basic_spec.rb +267 -0
- data/spec/elasticsearch/model/adapters/mongoid/multi_model_spec.rb +66 -0
- data/spec/elasticsearch/model/adapters/mongoid_spec.rb +235 -0
- data/spec/elasticsearch/model/adapters/multiple_spec.rb +125 -0
- data/spec/elasticsearch/model/callbacks_spec.rb +33 -0
- data/spec/elasticsearch/model/client_spec.rb +66 -0
- data/spec/elasticsearch/model/hash_wrapper_spec.rb +12 -0
- data/spec/elasticsearch/model/importing_spec.rb +214 -0
- data/spec/elasticsearch/model/indexing_spec.rb +918 -0
- data/spec/elasticsearch/model/module_spec.rb +101 -0
- data/spec/elasticsearch/model/multimodel_spec.rb +55 -0
- data/spec/elasticsearch/model/naming_inheritance_spec.rb +184 -0
- data/spec/elasticsearch/model/naming_spec.rb +186 -0
- data/spec/elasticsearch/model/proxy_spec.rb +107 -0
- data/spec/elasticsearch/model/response/aggregations_spec.rb +66 -0
- data/spec/elasticsearch/model/response/base_spec.rb +90 -0
- data/spec/elasticsearch/model/response/pagination/kaminari_spec.rb +410 -0
- data/spec/elasticsearch/model/response/pagination/will_paginate_spec.rb +262 -0
- data/spec/elasticsearch/model/response/records_spec.rb +118 -0
- data/spec/elasticsearch/model/response/response_spec.rb +131 -0
- data/spec/elasticsearch/model/response/result_spec.rb +122 -0
- data/spec/elasticsearch/model/response/results_spec.rb +56 -0
- data/spec/elasticsearch/model/searching_search_request_spec.rb +112 -0
- data/spec/elasticsearch/model/searching_spec.rb +49 -0
- data/spec/elasticsearch/model/serializing_spec.rb +22 -0
- data/spec/spec_helper.rb +161 -0
- data/spec/support/app.rb +21 -0
- data/spec/support/app/answer.rb +33 -0
- data/spec/support/app/article.rb +22 -0
- data/spec/support/app/article_for_pagination.rb +12 -0
- data/spec/support/app/article_with_custom_serialization.rb +13 -0
- data/spec/support/app/article_with_dynamic_index_name.rb +15 -0
- data/spec/support/app/author.rb +9 -0
- data/spec/support/app/authorship.rb +4 -0
- data/spec/support/app/category.rb +3 -0
- data/spec/support/app/comment.rb +3 -0
- data/spec/support/app/episode.rb +11 -0
- data/spec/support/app/image.rb +19 -0
- data/spec/support/app/import_article.rb +12 -0
- data/spec/support/app/mongoid_article.rb +21 -0
- data/spec/support/app/namespaced_book.rb +10 -0
- data/spec/support/app/parent_and_child_searchable.rb +24 -0
- data/spec/support/app/post.rb +14 -0
- data/spec/support/app/question.rb +27 -0
- data/spec/support/app/searchable.rb +48 -0
- data/spec/support/app/series.rb +11 -0
- data/spec/support/model.json +1 -0
- data/{test → spec}/support/model.yml +0 -0
- metadata +129 -86
- data/test/integration/active_record_associations_parent_child_test.rb +0 -188
- data/test/integration/active_record_associations_test.rb +0 -339
- data/test/integration/active_record_basic_test.rb +0 -255
- data/test/integration/active_record_custom_serialization_test.rb +0 -67
- data/test/integration/active_record_import_test.rb +0 -168
- data/test/integration/active_record_namespaced_model_test.rb +0 -56
- data/test/integration/active_record_pagination_test.rb +0 -149
- data/test/integration/dynamic_index_name_test.rb +0 -52
- data/test/integration/mongoid_basic_test.rb +0 -240
- data/test/integration/multiple_models_test.rb +0 -176
- data/test/support/model.json +0 -1
- data/test/test_helper.rb +0 -92
- data/test/unit/adapter_active_record_test.rb +0 -157
- data/test/unit/adapter_default_test.rb +0 -41
- data/test/unit/adapter_mongoid_test.rb +0 -161
- data/test/unit/adapter_multiple_test.rb +0 -106
- data/test/unit/adapter_test.rb +0 -69
- data/test/unit/callbacks_test.rb +0 -31
- data/test/unit/client_test.rb +0 -27
- data/test/unit/hash_wrapper_test.rb +0 -13
- data/test/unit/importing_test.rb +0 -224
- data/test/unit/indexing_test.rb +0 -720
- data/test/unit/module_test.rb +0 -68
- data/test/unit/multimodel_test.rb +0 -38
- data/test/unit/naming_inheritance_test.rb +0 -94
- data/test/unit/naming_test.rb +0 -103
- data/test/unit/proxy_test.rb +0 -98
- data/test/unit/response_aggregations_test.rb +0 -46
- data/test/unit/response_base_test.rb +0 -40
- data/test/unit/response_pagination_kaminari_test.rb +0 -433
- data/test/unit/response_pagination_will_paginate_test.rb +0 -398
- data/test/unit/response_records_test.rb +0 -91
- data/test/unit/response_result_test.rb +0 -90
- data/test/unit/response_results_test.rb +0 -34
- data/test/unit/response_test.rb +0 -104
- data/test/unit/searching_search_request_test.rb +0 -78
- data/test/unit/searching_test.rb +0 -41
- data/test/unit/serializing_test.rb +0 -17
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Elasticsearch::Model::Client do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
class ::DummyClientModel
|
7
|
+
extend Elasticsearch::Model::Client::ClassMethods
|
8
|
+
include Elasticsearch::Model::Client::InstanceMethods
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
after(:all) do
|
13
|
+
remove_classes(DummyClientModel)
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'when a class includes the client module class methods' do
|
17
|
+
|
18
|
+
it 'defines the client module class methods on the model' do
|
19
|
+
expect(DummyClientModel.client).to be_a(Elasticsearch::Transport::Client)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'when a class includes the client module instance methods' do
|
24
|
+
|
25
|
+
it 'defines the client module class methods on the model' do
|
26
|
+
expect(DummyClientModel.new.client).to be_a(Elasticsearch::Transport::Client)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'when the client is set on the class' do
|
31
|
+
|
32
|
+
around do |example|
|
33
|
+
original_client = DummyClientModel.client
|
34
|
+
DummyClientModel.client = 'foobar'
|
35
|
+
example.run
|
36
|
+
DummyClientModel.client = original_client
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'sets the client on the class' do
|
40
|
+
expect(DummyClientModel.client).to eq('foobar')
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'sets the client on an instance' do
|
44
|
+
expect(DummyClientModel.new.client).to eq('foobar')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
context 'when the client is set on an instance' do
|
49
|
+
|
50
|
+
before do
|
51
|
+
model_instance.client = 'foo'
|
52
|
+
end
|
53
|
+
|
54
|
+
let(:model_instance) do
|
55
|
+
DummyClientModel.new
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'sets the client on an instance' do
|
59
|
+
expect(model_instance.client).to eq('foo')
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'does not set the client on the class' do
|
63
|
+
expect(DummyClientModel.client).to be_a(Elasticsearch::Transport::Client)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Elasticsearch::Model::HashWrapper, if: Hashie::VERSION >= '3.5.3' do
|
4
|
+
|
5
|
+
before do
|
6
|
+
expect(Hashie.logger).to receive(:warn).never
|
7
|
+
end
|
8
|
+
|
9
|
+
it 'does not print a warning for re-defined methods' do
|
10
|
+
Elasticsearch::Model::HashWrapper.new(:foo => 'bar', :sort => true)
|
11
|
+
end
|
12
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Elasticsearch::Model::Importing do
|
4
|
+
|
5
|
+
before(:all) do
|
6
|
+
class DummyImportingModel
|
7
|
+
end
|
8
|
+
|
9
|
+
module DummyImportingAdapter
|
10
|
+
module ImportingMixin
|
11
|
+
def __find_in_batches(options={}, &block)
|
12
|
+
yield if block_given?
|
13
|
+
end
|
14
|
+
def __transform
|
15
|
+
lambda {|a|}
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def importing_mixin
|
20
|
+
ImportingMixin
|
21
|
+
end; module_function :importing_mixin
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
after(:all) do
|
26
|
+
remove_classes(DummyImportingModel, DummyImportingAdapter)
|
27
|
+
end
|
28
|
+
|
29
|
+
before do
|
30
|
+
allow(Elasticsearch::Model::Adapter).to receive(:from_class).with(DummyImportingModel).and_return(DummyImportingAdapter)
|
31
|
+
DummyImportingModel.__send__ :include, Elasticsearch::Model::Importing
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'when a model includes the Importing module' do
|
35
|
+
|
36
|
+
it 'provides importing methods' do
|
37
|
+
expect(DummyImportingModel.respond_to?(:import)).to be(true)
|
38
|
+
expect(DummyImportingModel.respond_to?(:__find_in_batches)).to be(true)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
describe '#import' do
|
43
|
+
|
44
|
+
before do
|
45
|
+
allow(DummyImportingModel).to receive(:index_name).and_return('foo')
|
46
|
+
allow(DummyImportingModel).to receive(:document_type).and_return('foo')
|
47
|
+
allow(DummyImportingModel).to receive(:index_exists?).and_return(true)
|
48
|
+
allow(DummyImportingModel).to receive(:__batch_to_bulk)
|
49
|
+
allow(client).to receive(:bulk).and_return(response)
|
50
|
+
end
|
51
|
+
|
52
|
+
let(:client) do
|
53
|
+
double('client')
|
54
|
+
end
|
55
|
+
|
56
|
+
let(:response) do
|
57
|
+
{ 'items' => [] }
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'when no options are provided' do
|
61
|
+
|
62
|
+
before do
|
63
|
+
expect(DummyImportingModel).to receive(:client).and_return(client)
|
64
|
+
allow(DummyImportingModel).to receive(:index_exists?).and_return(true)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'uses the client to import documents' do
|
68
|
+
expect(DummyImportingModel.import).to eq(0)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
context 'when there is an error' do
|
73
|
+
|
74
|
+
before do
|
75
|
+
expect(DummyImportingModel).to receive(:client).and_return(client)
|
76
|
+
allow(DummyImportingModel).to receive(:index_exists?).and_return(true)
|
77
|
+
end
|
78
|
+
|
79
|
+
let(:response) do
|
80
|
+
{ 'items' => [{ 'index' => { } }, { 'index' => { 'error' => 'FAILED' } }] }
|
81
|
+
end
|
82
|
+
|
83
|
+
it 'returns the number of errors' do
|
84
|
+
expect(DummyImportingModel.import).to eq(1)
|
85
|
+
end
|
86
|
+
|
87
|
+
context 'when the method is called with the option to return the errors' do
|
88
|
+
|
89
|
+
it 'returns the errors' do
|
90
|
+
expect(DummyImportingModel.import(return: 'errors')).to eq([{ 'index' => { 'error' => 'FAILED' } }])
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'when the method is called with a block' do
|
95
|
+
|
96
|
+
it 'yields the response to the block' do
|
97
|
+
DummyImportingModel.import do |response|
|
98
|
+
expect(response['items'].size).to eq(2)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
context 'when the index does not exist' do
|
105
|
+
|
106
|
+
before do
|
107
|
+
allow(DummyImportingModel).to receive(:index_exists?).and_return(false)
|
108
|
+
end
|
109
|
+
|
110
|
+
it 'raises an exception' do
|
111
|
+
expect {
|
112
|
+
DummyImportingModel.import
|
113
|
+
}.to raise_exception(ArgumentError)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
context 'when the method is called with the force option' do
|
118
|
+
|
119
|
+
before do
|
120
|
+
expect(DummyImportingModel).to receive(:create_index!).with(force: true, index: 'foo').and_return(true)
|
121
|
+
expect(DummyImportingModel).to receive(:__find_in_batches).with(foo: 'bar').and_return(true)
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'deletes and creates the index' do
|
125
|
+
expect(DummyImportingModel.import(force: true, foo: 'bar')).to eq(0)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
context 'when the method is called with the refresh option' do
|
130
|
+
|
131
|
+
before do
|
132
|
+
expect(DummyImportingModel).to receive(:refresh_index!).with(index: 'foo').and_return(true)
|
133
|
+
expect(DummyImportingModel).to receive(:__find_in_batches).with(foo: 'bar').and_return(true)
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'refreshes the index' do
|
137
|
+
expect(DummyImportingModel.import(refresh: true, foo: 'bar')).to eq(0)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'when a different index name is provided' do
|
142
|
+
|
143
|
+
before do
|
144
|
+
expect(DummyImportingModel).to receive(:client).and_return(client)
|
145
|
+
expect(client).to receive(:bulk).with(body: nil, index: 'my-new-index', type: 'foo').and_return(response)
|
146
|
+
end
|
147
|
+
|
148
|
+
it 'uses the alternate index name' do
|
149
|
+
expect(DummyImportingModel.import(index: 'my-new-index')).to eq(0)
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
context 'when a different document type is provided' do
|
154
|
+
|
155
|
+
before do
|
156
|
+
expect(DummyImportingModel).to receive(:client).and_return(client)
|
157
|
+
expect(client).to receive(:bulk).with(body: nil, index: 'foo', type: 'my-new-type').and_return(response)
|
158
|
+
end
|
159
|
+
|
160
|
+
it 'uses the alternate index name' do
|
161
|
+
expect(DummyImportingModel.import(type: 'my-new-type')).to eq(0)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
context 'the transform method' do
|
166
|
+
|
167
|
+
before do
|
168
|
+
expect(DummyImportingModel).to receive(:client).and_return(client)
|
169
|
+
expect(DummyImportingModel).to receive(:__transform).and_return(transform)
|
170
|
+
expect(DummyImportingModel).to receive(:__batch_to_bulk).with(anything, transform)
|
171
|
+
end
|
172
|
+
|
173
|
+
let(:transform) do
|
174
|
+
lambda {|a|}
|
175
|
+
end
|
176
|
+
|
177
|
+
it 'applies the transform method to the results' do
|
178
|
+
expect(DummyImportingModel.import).to eq(0)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
context 'when a transform is provided as an option' do
|
183
|
+
|
184
|
+
context 'when the transform option is not a lambda' do
|
185
|
+
|
186
|
+
let(:transform) do
|
187
|
+
'not_callable'
|
188
|
+
end
|
189
|
+
|
190
|
+
it 'raises an error' do
|
191
|
+
expect {
|
192
|
+
DummyImportingModel.import(transform: transform)
|
193
|
+
}.to raise_exception(ArgumentError)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
context 'when the transform option is a lambda' do
|
198
|
+
|
199
|
+
before do
|
200
|
+
expect(DummyImportingModel).to receive(:client).and_return(client)
|
201
|
+
expect(DummyImportingModel).to receive(:__batch_to_bulk).with(anything, transform)
|
202
|
+
end
|
203
|
+
|
204
|
+
let(:transform) do
|
205
|
+
lambda {|a|}
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'applies the transform lambda to the results' do
|
209
|
+
expect(DummyImportingModel.import(transform: transform)).to eq(0)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -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
|