elastic_searchable 1.6 → 2.0.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.
@@ -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