elastic_searchable 1.6 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,433 +0,0 @@
1
- require 'spec_helper'
2
-
3
- describe ElasticSearchable do
4
- before do
5
- begin
6
- ElasticSearchable.delete '/elastic_searchable'
7
- rescue
8
- end
9
- end
10
-
11
- describe 'an ActiveRecord class that has not invoked elastic_searchable' do
12
- before do
13
- stub_const('Parent', Class.new(ActiveRecord::Base))
14
- end
15
- let(:clazz) { Parent }
16
- let(:instance) { clazz.new }
17
- it do
18
- expect(clazz).to_not respond_to :elastic_options
19
- expect(instance).to_not respond_to :percolations
20
- end
21
- end
22
-
23
- describe 'with an ActiveRecord class with elastic_searchable config' do
24
- let(:clazz) { Post }
25
- let(:instance) { Post.new }
26
- it do
27
- expect(clazz).to respond_to :search
28
- expect(clazz).to respond_to :elastic_options
29
- expect(clazz.elastic_options[:unless]).to include :elasticsearch_offline?
30
- expect(instance).to respond_to :percolations
31
- expect(instance.percolations).to eq []
32
- end
33
-
34
- describe '.request' do
35
- subject(:sending_request) { ElasticSearchable.request method, url }
36
- context 'GET' do
37
- let(:method) { :get }
38
- context 'with invalid url' do
39
- let(:url) { '/elastic_searchable/foobar/notfound' }
40
- it { expect { sending_request }.to raise_error ElasticSearchable::ElasticError }
41
- end
42
- end
43
- end
44
-
45
- describe '.create_index' do
46
- let(:index_status) { ElasticSearchable.request :get, '/elastic_searchable/_status' }
47
- context 'when it has not been called' do
48
- it { expect { index_status }.to raise_error }
49
- end
50
- context 'when it has been called' do
51
- before do
52
- Post.create_index
53
- Post.refresh_index
54
- end
55
- it { expect { index_status }.not_to raise_error }
56
- end
57
- end
58
-
59
- describe 'create callbacks' do
60
- let!(:post) { Post.create :title => 'foo', :body => "bar" }
61
- it 'fires index callbacks' do
62
- expect(post).to be_indexed
63
- expect(post).to be_indexed_on_create
64
- expect(post).not_to be_indexed_on_update
65
- end
66
- end
67
-
68
- describe 'update callbacks' do
69
- before do
70
- Post.create :title => 'foo', :body => 'bar'
71
- end
72
- let(:post) do
73
- Post.last.tap do |post|
74
- post.title = 'baz'
75
- post.save
76
- end
77
- end
78
- it do
79
- expect(post).to be_indexed
80
- expect(post).not_to be_indexed_on_create
81
- expect(post).to be_indexed_on_update
82
- end
83
- end
84
-
85
- describe 'ElasticSearchable.offline' do
86
- let!(:post) do
87
- ElasticSearchable.offline do
88
- Post.create :title => 'foo', :body => "bar"
89
- end
90
- end
91
- it do
92
- expect(post).not_to be_indexed
93
- expect(post).not_to be_indexed_on_create
94
- expect(post).not_to be_indexed_on_update
95
- end
96
- end
97
-
98
- context 'with an empty index and multiple database records' do
99
- before do
100
- Post.delete_all
101
- Post.create_index
102
- Post.create :title => 'foo', :body => "first bar"
103
- Post.create :title => 'foo', :body => "second bar"
104
- Post.delete_index
105
- Post.create_index
106
- Post.refresh_index
107
- end
108
- let!(:first_post) { Post.where(:body => "first bar").first }
109
- let!(:second_post) { Post.where(:body => "second bar").first }
110
- it 'does not raise error if an error occurs when reindexing model' do
111
- expect_any_instance_of(Logger).to receive(:warn).at_least(:once)
112
- expect(ElasticSearchable).to receive(:request).and_raise(ElasticSearchable::ElasticError.new('faux error'))
113
- expect { Post.reindex }.not_to raise_error
114
- end
115
- it 'does not raise error when destroying one instance' do
116
- expect_any_instance_of(Logger).to receive(:warn).at_least(:once)
117
- expect { first_post.destroy }.not_to raise_error
118
- end
119
- describe ".reindex" do
120
- before do
121
- Post.reindex :per_page => 1, :scope => Post.order('body desc')
122
- Post.refresh_index
123
- end
124
- it do
125
- expect { ElasticSearchable.request :get, "/elastic_searchable/posts/#{first_post.id}" }.to_not raise_error
126
- expect { ElasticSearchable.request :get, "/elastic_searchable/posts/#{second_post.id}" }.to_not raise_error
127
- end
128
- end
129
- end
130
-
131
- context 'with the index containing multiple results' do
132
- before do
133
- Post.create_index
134
- Post.create :title => 'foo', :body => "first bar"
135
- Post.create :title => 'foo', :body => "second bar"
136
- Post.refresh_index
137
- end
138
- let!(:first_post) { Post.where(:body => "first bar").first }
139
- let!(:second_post) { Post.where(:body => "second bar").first }
140
-
141
- context 'searching on a term that returns one result' do
142
- subject(:results) { Post.search 'first' }
143
- it do
144
- is_expected.to include first_post
145
- expect(results.current_page).to eq 1
146
- expect(results.per_page).to eq Post.per_page
147
- expect(results.previous_page).to be_nil
148
- expect(results.next_page).to be_nil
149
- expect(results.first.hit['_id']).to eq first_post.id.to_s
150
- end
151
- end
152
- context 'searching on a term that returns multiple results' do
153
- subject(:results) { Post.search 'foo' }
154
- it do
155
- is_expected.to include first_post
156
- is_expected.to include second_post
157
- expect(results.current_page).to eq 1
158
- expect(results.per_page).to eq Post.per_page
159
- expect(results.previous_page).to be_nil
160
- expect(results.next_page).to be_nil
161
- expect(results.first.hit['_id']).to eq first_post.id.to_s
162
- expect(results.last.hit['_id']).to eq second_post.id.to_s
163
- end
164
- end
165
- context 'searching for results using a query Hash' do
166
- subject(:results) do
167
- Post.search({
168
- :filtered => {
169
- :query => {
170
- :term => {:title => 'foo'},
171
- },
172
- :filter => {
173
- :or => [
174
- {:term => {:body => 'second'}},
175
- {:term => {:body => 'third'}}
176
- ]
177
- }
178
- }
179
- })
180
- end
181
- it do
182
- is_expected.to_not include first_post
183
- is_expected.to include second_post
184
- end
185
- end
186
-
187
- context 'when per_page is a string' do
188
- subject(:results) { Post.search 'foo', :per_page => 1.to_s, :sort => 'id' }
189
- it { expect(results).to include first_post }
190
- end
191
-
192
- context 'searching for second page using will_paginate params' do
193
- subject(:results) { Post.search 'foo', :page => 2, :per_page => 1, :sort => 'id' }
194
- it do
195
- expect(results).not_to include first_post
196
- expect(results).to include second_post
197
- expect(results.current_page).to eq 2
198
- expect(results.per_page).to eq 1
199
- expect(results.previous_page).to eq 1
200
- expect(results.next_page).to be_nil
201
- end
202
- end
203
-
204
- context 'sorting search results' do
205
- subject(:results) { Post.search 'foo', :sort => 'id:desc' }
206
- it 'sorts results correctly' do
207
- expect(results).to eq [second_post, first_post]
208
- end
209
- end
210
-
211
- context 'advanced sort options' do
212
- subject(:results) { Post.search 'foo', :sort => [{:id => 'desc'}] }
213
- it 'sorts results correctly' do
214
- expect(results).to eq [second_post, first_post]
215
- end
216
- end
217
-
218
- context 'destroying one object' do
219
- before do
220
- first_post.destroy
221
- Post.refresh_index
222
- end
223
- it 'is removed from the index' do
224
- expect(ElasticSearchable.get("/elastic_searchable/posts/#{first_post.id}").response).to be_a Net::HTTPNotFound
225
- end
226
- end
227
- end
228
-
229
- context 'deleting a record without updating the index' do
230
-
231
- context 'backfilling partial result pages' do
232
- let!(:posts) do
233
- posts = (1..8).map do |i|
234
- Post.create :title => 'foo', :body => "#{i} bar"
235
- end
236
- Post.refresh_index
237
- posts
238
- end
239
- subject(:results) { Post.search 'foo', :size => 4, :sort => 'id:desc' }
240
- it 'backfills the first page with results from other pages' do
241
- removed_posts = []
242
- posts.each_with_index do |post, i|
243
- if i % 2 == 1
244
- removed_posts << post
245
- expect(Post).to receive(:delete_id_from_index_backgrounded).with(post.id)
246
- post.delete
247
- end
248
- end
249
- expect(results).to match_array(posts - removed_posts)
250
- expect(results.total_entries).to eq 4
251
- end
252
- end
253
- end
254
- end
255
-
256
- context 'activerecord class with optional :if=>proc configuration' do
257
- context 'when creating new instance' do
258
- it do
259
- expect_any_instance_of(Blog).to_not receive(:reindex)
260
- blog = Blog.create! :title => 'foo'
261
- expect(ElasticSearchable.get("/elastic_searchable/blogs/#{blog.id}").response).to be_a Net::HTTPNotFound
262
- end
263
- end
264
- end
265
-
266
- context 'activerecord class with :index_options and :mapping' do
267
- context 'creating index' do
268
- before do
269
- User.create_index
270
- end
271
- it 'uses custom index_options' do
272
- settings = ElasticSearchable.request(:get, '/elastic_searchable/_settings')['elastic_searchable']['settings']['index']
273
- settings.delete('version')
274
- settings.delete('uuid')
275
- expect(settings).to eq(
276
- "analysis" => {
277
- "analyzer" => {
278
- "default"=> {
279
- "filter" => [ "standard", "lowercase", "porterStem"],
280
- "tokenizer" => "standard"
281
- }
282
- }
283
- },
284
- "number_of_shards"=>"1",
285
- "number_of_replicas"=>"0"
286
- )
287
- end
288
- it 'has set mapping' do
289
- status = ElasticSearchable.request :get, '/elastic_searchable/users/_mapping'
290
- expect(status['elastic_searchable']['mappings']['users']['properties']).to eq(
291
- "name"=> {"type"=>"string", "index"=>"not_analyzed"}
292
- )
293
- end
294
- end
295
- end
296
-
297
- context 'activerecord class with optional :json config' do
298
- context 'creating index' do
299
- let!(:friend) do
300
- Friend.create_index
301
- book = Book.create! :isbn => '123abc', :title => 'another world'
302
- friend = Friend.new :name => 'bob', :favorite_color => 'red'
303
- friend.book = book
304
- friend.save!
305
- Friend.refresh_index
306
- friend
307
- end
308
- subject(:json) { ElasticSearchable.request(:get, "/elastic_searchable/friends/#{friend.id}")['_source'] }
309
- it 'indexes json with configuration' do
310
- expect(json['favorite_color']).to be_nil
311
- expect(json['book'].key?('isbn')).to be_falsey
312
- expect(json).to eq(
313
- "name" => 'bob',
314
- 'book' => { 'title' => 'another world' }
315
- )
316
- end
317
- end
318
- end
319
-
320
- context 'updating ElasticSearchable.default_index' do
321
- before do
322
- ElasticSearchable.default_index = 'my_new_index'
323
- end
324
- after do
325
- ElasticSearchable.default_index = ElasticSearchable::DEFAULT_INDEX
326
- end
327
- it { expect(ElasticSearchable.default_index).to eq 'my_new_index' }
328
- end
329
-
330
- context 'Book class with after_percolate callback' do
331
- context 'with created index and populated fields' do
332
- before do
333
- Book.create_index
334
- Book.create! :title => 'baz'
335
- end
336
- context "when index has configured percolation" do
337
- before do
338
- ElasticSearchable.request :put, '/elastic_searchable/.percolator/myfilter', :json_body => {:query => {:query_string => {:query => 'foo' }}}
339
- ElasticSearchable.request :post, '/elastic_searchable/_refresh'
340
- end
341
- context 'creating an object that does not match the percolation' do
342
- it 'does not percolate the record' do
343
- expect_any_instance_of(Book).to_not receive(:on_percolated)
344
- Book.create! :title => 'bar'
345
- end
346
- end
347
- context 'creating an object that matches the percolation' do
348
- let!(:book) do
349
- Book.create :title => "foo"
350
- end
351
- it do
352
- expect(book.percolated).to eq ['myfilter']
353
- end
354
- end
355
- context 'percolating a non-persisted object' do
356
- let!(:matches) { Book.new(:title => 'foo').percolate }
357
- it do
358
- expect(matches).to eq ['myfilter']
359
- end
360
- end
361
- context "with multiple percolators in the index" do
362
- before do
363
- ElasticSearchable.request :put, '/elastic_searchable/.percolator/greenfilter', :json_body => { :color => 'green', :query => {:query_string => {:query => 'foo' }}}
364
- ElasticSearchable.request :put, '/elastic_searchable/.percolator/bluefilter', :json_body => { :color => 'blue', :query => {:query_string => {:query => 'foo' }}}
365
- ElasticSearchable.request :post, '/elastic_searchable/_refresh'
366
- end
367
- context 'percolating a non-persisted object with no limitation' do
368
- let!(:matches) { Book.new(:title => 'foo').percolate }
369
- it 'returns all percolated matches' do
370
- expect(matches).to match_array ['myfilter', 'greenfilter', 'bluefilter']
371
- expect(matches.size).to eq 3
372
- end
373
- end
374
- context 'percolating a non-persisted object with limitations' do
375
- let!(:matches) { Book.new(:title => 'foo').percolate(:term => { :color => 'green' }) }
376
- it 'returns limited percolated matches' do
377
- expect(matches).to eq ['greenfilter']
378
- end
379
- end
380
- end
381
- end
382
- end
383
- end
384
-
385
- context 'with 2 MaxPageSizeClass instances' do
386
- before do
387
- MaxPageSizeClass.create_index
388
- MaxPageSizeClass.create! :name => 'foo one'
389
- MaxPageSizeClass.create! :name => 'foo two'
390
- MaxPageSizeClass.refresh_index
391
- end
392
- let!(:first) { MaxPageSizeClass.where(:name => 'foo one').first }
393
- let!(:second) { MaxPageSizeClass.where(:name => 'foo two').first }
394
- subject(:results) { MaxPageSizeClass.search 'foo' }
395
- context 'MaxPageSizeClass.search with default options and WillPaginate' do
396
- before do
397
- ElasticSearchable::Paginator.handler = ElasticSearchable::Pagination::WillPaginate
398
- end
399
- it do
400
- expect(results.per_page).to eq 1
401
- expect(results.length).to eq 1
402
- expect(results.total_entries).to eq 2
403
- end
404
- end
405
-
406
- context 'MaxPageSizeClass.search with default options and Kaminari' do
407
- before do
408
- ElasticSearchable::Paginator.handler = ElasticSearchable::Pagination::Kaminari
409
- @results = MaxPageSizeClass.search 'foo'
410
- end
411
- it do
412
- expect(results.per_page).to eq 1
413
- expect(results.length).to eq 1
414
- expect(results.total_entries).to eq 2
415
- expect(results.num_pages).to eq 2
416
- end
417
- end
418
-
419
- describe '.escape_query' do
420
- let(:backslash) { "\\" }
421
- shared_examples_for "escaped" do
422
- it { expect(ElasticSearchable.escape_query(queryString)).to eq(backslash + queryString) }
423
- end
424
- %w| ! ^ + - { } [ ] ~ * : ? ( ) "|.each do |mark|
425
- context "escaping '#{mark}'" do
426
- let(:queryString) { mark }
427
- it_behaves_like "escaped"
428
- end
429
- end
430
- end
431
- end
432
- end
433
-
data/spec/support/blog.rb DELETED
@@ -1,6 +0,0 @@
1
- class Blog < ActiveRecord::Base
2
- elastic_searchable :if => proc {|b| b.should_index? }, :index_options => SINGLE_NODE_CLUSTER_CONFIG
3
- def should_index?
4
- false
5
- end
6
- end
data/spec/support/book.rb DELETED
@@ -1,10 +0,0 @@
1
- class Book < ActiveRecord::Base
2
- elastic_searchable
3
- after_percolate :on_percolated
4
- def on_percolated
5
- @percolated = percolations
6
- end
7
- def percolated
8
- @percolated
9
- end
10
- end
@@ -1,3 +0,0 @@
1
- sqlite:
2
- adapter: sqlite3
3
- database: spec/elastic_searchable.sqlite3
@@ -1,4 +0,0 @@
1
- class Friend < ActiveRecord::Base
2
- belongs_to :book
3
- elastic_searchable :json => {:include => {:book => {:only => :title}}, :only => :name}, :index_options => SINGLE_NODE_CLUSTER_CONFIG
4
- end
@@ -1,6 +0,0 @@
1
- class MaxPageSizeClass < ActiveRecord::Base
2
- elastic_searchable :index_options => SINGLE_NODE_CLUSTER_CONFIG
3
- def self.max_per_page
4
- 1
5
- end
6
- end
data/spec/support/post.rb DELETED
@@ -1,26 +0,0 @@
1
- class Post < ActiveRecord::Base
2
- Post.class_eval do
3
- elastic_searchable :index_options => SINGLE_NODE_CLUSTER_CONFIG
4
- after_index :indexed
5
- after_index :indexed_on_create, :on => :create
6
- after_index :indexed_on_update, :on => :update
7
- def indexed
8
- @indexed = true
9
- end
10
- def indexed?
11
- @indexed
12
- end
13
- def indexed_on_create
14
- @indexed_on_create = true
15
- end
16
- def indexed_on_create?
17
- @indexed_on_create
18
- end
19
- def indexed_on_update
20
- @indexed_on_update = true
21
- end
22
- def indexed_on_update?
23
- @indexed_on_update
24
- end
25
- end
26
- end
data/spec/support/user.rb DELETED
@@ -1,8 +0,0 @@
1
- class User < ActiveRecord::Base
2
- elastic_searchable :index_options => {
3
- 'number_of_replicas' => 0,
4
- 'number_of_shards' => 1,
5
- "analysis.analyzer.default.tokenizer" => 'standard',
6
- "analysis.analyzer.default.filter" => ["standard", "lowercase", 'porterStem']},
7
- :mapping => {:properties => {:name => {:type => 'string', :index => 'not_analyzed'}}}
8
- end