tire 0.5.4 → 0.5.5
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +3 -0
- data/README.markdown +49 -29
- data/examples/rails-application-template.rb +15 -15
- data/examples/tire-dsl.rb +24 -24
- data/lib/tire.rb +2 -1
- data/lib/tire/alias.rb +3 -3
- data/lib/tire/dsl.rb +17 -19
- data/lib/tire/index.rb +67 -10
- data/lib/tire/model/callbacks.rb +1 -1
- data/lib/tire/model/indexing.rb +2 -2
- data/lib/tire/model/naming.rb +1 -1
- data/lib/tire/model/percolate.rb +3 -3
- data/lib/tire/model/persistence.rb +1 -1
- data/lib/tire/model/persistence/attributes.rb +2 -2
- data/lib/tire/model/persistence/storage.rb +2 -2
- data/lib/tire/model/search.rb +8 -8
- data/lib/tire/multi_search.rb +4 -4
- data/lib/tire/rubyext/ruby_1_8.rb +1 -1
- data/lib/tire/search.rb +1 -1
- data/lib/tire/search/scan.rb +1 -1
- data/lib/tire/tasks.rb +1 -1
- data/lib/tire/version.rb +7 -11
- data/test/integration/active_record_searchable_test.rb +42 -0
- data/test/integration/dis_max_queries_test.rb +1 -1
- data/test/integration/dsl_search_test.rb +9 -0
- data/test/integration/index_mapping_test.rb +82 -7
- data/test/integration/persistent_model_test.rb +17 -0
- data/test/integration/sort_test.rb +16 -0
- data/test/models/active_record_models.rb +9 -0
- data/test/models/persistent_article.rb +1 -1
- data/test/models/persistent_article_in_index.rb +1 -1
- data/test/models/persistent_article_in_namespace.rb +1 -1
- data/test/models/persistent_article_with_percolation.rb +5 -0
- data/test/models/persistent_articles_with_custom_index_name.rb +1 -1
- data/test/test_helper.rb +1 -1
- data/test/unit/index_alias_test.rb +1 -1
- data/test/unit/index_test.rb +79 -1
- data/test/unit/model_persistence_test.rb +11 -1
- data/test/unit/model_search_test.rb +2 -2
- data/test/unit/tire_test.rb +11 -7
- data/tire.gemspec +4 -3
- metadata +61 -51
@@ -32,6 +32,10 @@ module Tire
|
|
32
32
|
create_table :active_record_class_with_dynamic_index_names do |t|
|
33
33
|
t.string :title
|
34
34
|
end
|
35
|
+
create_table :active_record_model_with_percolations do |t|
|
36
|
+
t.string :title
|
37
|
+
t.datetime :created_at, :default => 'NOW()'
|
38
|
+
end
|
35
39
|
end
|
36
40
|
end
|
37
41
|
|
@@ -158,6 +162,16 @@ module Tire
|
|
158
162
|
assert hit['_score'] > 0
|
159
163
|
end
|
160
164
|
end
|
165
|
+
|
166
|
+
should "provide access to highlighted fields in hit" do
|
167
|
+
results = ActiveRecordArticle.search :load => true do
|
168
|
+
query { string '"Test 1" OR "Test 2"' }
|
169
|
+
highlight :title
|
170
|
+
end
|
171
|
+
results.each_with_hit do |result, hit|
|
172
|
+
assert_equal 1, hit['highlight']['title'].size
|
173
|
+
end
|
174
|
+
end
|
161
175
|
end
|
162
176
|
|
163
177
|
context "with pagination" do
|
@@ -295,6 +309,16 @@ module Tire
|
|
295
309
|
assert_nil results.first
|
296
310
|
end
|
297
311
|
|
312
|
+
should "find second page with four loaded models" do
|
313
|
+
results = ActiveRecordArticle.search :load => true, :page => 2, :per_page => 5 do |search|
|
314
|
+
search.query { |query| query.string @q }
|
315
|
+
search.sort { by :title }
|
316
|
+
end
|
317
|
+
assert_equal 4, results.size
|
318
|
+
assert results.all? { |r| assert_instance_of ActiveRecordArticle, r }
|
319
|
+
assert_equal 'Test6', results.first.title
|
320
|
+
end
|
321
|
+
|
298
322
|
end
|
299
323
|
|
300
324
|
context "with from/size" do
|
@@ -571,6 +595,24 @@ module Tire
|
|
571
595
|
end
|
572
596
|
|
573
597
|
end
|
598
|
+
|
599
|
+
context "percolated search" do
|
600
|
+
setup do
|
601
|
+
ActiveRecordModelWithPercolation.index.register_percolator_query('alert') { string 'warning' }
|
602
|
+
Tire.index('_percolator').refresh
|
603
|
+
end
|
604
|
+
|
605
|
+
should "return matching queries when percolating" do
|
606
|
+
a = ActiveRecordModelWithPercolation.new :title => 'Warning!'
|
607
|
+
assert_contains a.percolate, 'alert'
|
608
|
+
end
|
609
|
+
|
610
|
+
should "return matching queries when saving" do
|
611
|
+
a = ActiveRecordModelWithPercolation.create! :title => 'Warning!'
|
612
|
+
assert_contains a.matches, 'alert'
|
613
|
+
end
|
614
|
+
end
|
615
|
+
|
574
616
|
end
|
575
617
|
|
576
618
|
end
|
@@ -13,6 +13,15 @@ module Tire
|
|
13
13
|
|
14
14
|
assert_equal 2, s.results.count
|
15
15
|
assert_equal 2, s.results.facets['tags']['count']
|
16
|
+
assert_match /_search\?pretty' -d '{/, s.to_curl, 'Make sure to ignore payload in URL params'
|
17
|
+
end
|
18
|
+
|
19
|
+
should "allow passing URL parameters" do
|
20
|
+
s = Tire.search 'articles-test', search_type: 'count', query: { match: { tags: 'ruby' } }
|
21
|
+
|
22
|
+
assert_equal 0, s.results.count
|
23
|
+
assert_equal 2, s.results.total
|
24
|
+
assert_match /_search.*search_type=count.*' -d '{/, s.to_curl
|
16
25
|
end
|
17
26
|
|
18
27
|
should "allow building search query iteratively" do
|
@@ -8,7 +8,7 @@ module Tire
|
|
8
8
|
context "Default mapping" do
|
9
9
|
teardown { Tire.index('mapped-index').delete; sleep 0.1 }
|
10
10
|
|
11
|
-
should "create and return the default mapping" do
|
11
|
+
should "create and return the default mapping as a Hash" do
|
12
12
|
|
13
13
|
index = Tire.index 'mapped-index' do
|
14
14
|
create
|
@@ -24,17 +24,92 @@ module Tire
|
|
24
24
|
|
25
25
|
context "Creating index with mapping" do
|
26
26
|
teardown { Tire.index('mapped-index').delete; sleep 0.1 }
|
27
|
-
|
27
|
+
|
28
28
|
should "create the specified mapping" do
|
29
|
-
|
30
29
|
index = Tire.index 'mapped-index' do
|
31
|
-
create
|
30
|
+
create mappings: {
|
31
|
+
article: {
|
32
|
+
_all: { enabled: false },
|
33
|
+
properties: {
|
34
|
+
title: { type: 'string', boost: 2.0, store: 'yes' }
|
35
|
+
}
|
36
|
+
}
|
37
|
+
}
|
32
38
|
end
|
33
39
|
|
34
|
-
# p index.mapping
|
40
|
+
# p index.mapping
|
41
|
+
assert_equal false, index.mapping['article']['_all']['enabled'], index.mapping.inspect
|
35
42
|
assert_equal 2.0, index.mapping['article']['properties']['title']['boost'], index.mapping.inspect
|
36
|
-
|
37
|
-
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "Update mapping" do
|
47
|
+
setup { Tire.index("mapped-index").create; sleep 1 }
|
48
|
+
teardown { Tire.index("mapped-index").delete; sleep 0.1 }
|
49
|
+
|
50
|
+
should "update the mapping for type" do
|
51
|
+
index = Tire.index("mapped-index")
|
52
|
+
|
53
|
+
index.mapping "article", :properties => { :body => { :type => "string" } }
|
54
|
+
assert_equal({ "type" => "string" }, index.mapping["article"]["properties"]["body"])
|
55
|
+
|
56
|
+
assert index.mapping("article", :properties => { :title => { :type => "string" } })
|
57
|
+
|
58
|
+
mapping = index.mapping
|
59
|
+
|
60
|
+
# Verify return value
|
61
|
+
assert mapping, index.response.inspect
|
62
|
+
|
63
|
+
# Verify response
|
64
|
+
assert_equal( { "type" => "string" }, mapping["article"]["properties"]["body"] )
|
65
|
+
assert_equal( { "type" => "string" }, mapping["article"]["properties"]["title"] )
|
66
|
+
end
|
67
|
+
|
68
|
+
should "fail to update the mapping in an incompatible way" do
|
69
|
+
index = Tire.index("mapped-index")
|
70
|
+
|
71
|
+
# 1. Update initial index mapping
|
72
|
+
assert index.mapping "article", properties: { body: { type: "string" } }
|
73
|
+
assert_equal( { "type" => "string" }, index.mapping["article"]["properties"]["body"] )
|
74
|
+
|
75
|
+
# 2. Attempt to update the mapping in incompatible way (change property type)
|
76
|
+
mapping = index.mapping "article", :properties => { :body => { :type => "integer" } }
|
77
|
+
|
78
|
+
# Verify return value
|
79
|
+
assert !mapping, index.response.inspect
|
80
|
+
#
|
81
|
+
# Verify response
|
82
|
+
assert_match /MergeMappingException/, index.response.body
|
83
|
+
end
|
84
|
+
|
85
|
+
should "honor the `ignore_conflicts` option" do
|
86
|
+
index = Tire.index("mapped-index")
|
87
|
+
|
88
|
+
# 1. Update initial index mapping
|
89
|
+
assert index.mapping "article", properties: { body: { type: "string" } }
|
90
|
+
assert_equal( { "type" => "string" }, index.mapping["article"]["properties"]["body"] )
|
91
|
+
|
92
|
+
# 2. Attempt to update the mapping in incompatible way and ignore conflicts
|
93
|
+
mapping = index.mapping "article", ignore_conflicts: true, properties: { body: { type: "integer" } }
|
94
|
+
|
95
|
+
# Verify return value (true since we ignore conflicts)
|
96
|
+
assert mapping, index.response.inspect
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
context "Delete mapping" do
|
102
|
+
setup { Tire.index("mapped-index").create; sleep 1 }
|
103
|
+
teardown { Tire.index("mapped-index").delete; sleep 0.1 }
|
104
|
+
|
105
|
+
should "delete the mapping for type" do
|
106
|
+
index = Tire.index("mapped-index")
|
107
|
+
|
108
|
+
# 1. Update initial index mapping
|
109
|
+
assert index.mapping 'article', properties: { body: { type: "string" } }
|
110
|
+
|
111
|
+
assert index.delete_mapping 'article'
|
112
|
+
assert index.mapping.empty?, index.response.inspect
|
38
113
|
end
|
39
114
|
end
|
40
115
|
|
@@ -182,6 +182,23 @@ module Tire
|
|
182
182
|
end
|
183
183
|
end
|
184
184
|
|
185
|
+
context "percolated search" do
|
186
|
+
setup do
|
187
|
+
PersistentArticleWithPercolation.index.register_percolator_query('alert') { string 'warning' }
|
188
|
+
Tire.index('_percolator').refresh
|
189
|
+
end
|
190
|
+
|
191
|
+
should "return matching queries when percolating" do
|
192
|
+
a = PersistentArticleWithPercolation.new :title => 'Warning!'
|
193
|
+
assert_contains a.percolate, 'alert'
|
194
|
+
end
|
195
|
+
|
196
|
+
should "return matching queries when saving" do
|
197
|
+
a = PersistentArticleWithPercolation.create :title => 'Warning!'
|
198
|
+
assert_contains a.matches, 'alert'
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
185
202
|
end
|
186
203
|
|
187
204
|
end
|
@@ -29,6 +29,22 @@ module Tire
|
|
29
29
|
assert_equal 'Two', s.results.first[:title]
|
30
30
|
end
|
31
31
|
|
32
|
+
should "sort by multiple fields" do
|
33
|
+
q = '*'
|
34
|
+
s = Tire.search('articles-test') do
|
35
|
+
query { string q }
|
36
|
+
sort do
|
37
|
+
by :words, :desc
|
38
|
+
by :title, :asc
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# p s.results.to_a.map { |i| [i.title, i.sort] }
|
43
|
+
assert_equal 'Three', s.results[0][:title]
|
44
|
+
assert_equal 'Four', s.results[1][:title]
|
45
|
+
assert_equal 'Two', s.results[2][:title]
|
46
|
+
end
|
47
|
+
|
32
48
|
end
|
33
49
|
|
34
50
|
end
|
@@ -120,3 +120,12 @@ class ActiveRecordNamespace::MyModel < ActiveRecord::Base
|
|
120
120
|
include Tire::Model::Search
|
121
121
|
include Tire::Model::Callbacks
|
122
122
|
end
|
123
|
+
|
124
|
+
# Model with percolation
|
125
|
+
|
126
|
+
class ActiveRecordModelWithPercolation < ActiveRecord::Base
|
127
|
+
include Tire::Model::Search
|
128
|
+
include Tire::Model::Callbacks
|
129
|
+
|
130
|
+
percolate!
|
131
|
+
end
|
data/test/test_helper.rb
CHANGED
@@ -84,7 +84,7 @@ module Test::Integration
|
|
84
84
|
begin
|
85
85
|
::RestClient.get URL
|
86
86
|
rescue Errno::ECONNREFUSED
|
87
|
-
abort "\n\n#{'-'*87}\n[ABORTED] You have to run
|
87
|
+
abort "\n\n#{'-'*87}\n[ABORTED] You have to run Elasticsearch on #{URL} for integration tests\n#{'-'*87}\n\n"
|
88
88
|
end
|
89
89
|
|
90
90
|
::RestClient.delete "#{URL}/articles-test" rescue nil
|
@@ -130,7 +130,7 @@ module Tire
|
|
130
130
|
|
131
131
|
context "saving" do
|
132
132
|
|
133
|
-
should "send data to
|
133
|
+
should "send data to Elasticsearch" do
|
134
134
|
Configuration.client.expects(:post).with do |url, json|
|
135
135
|
url == "#{Configuration.url}/_aliases" &&
|
136
136
|
json =~ /"index":"index_2012_05"/ &&
|
data/test/unit/index_test.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
1
3
|
require 'test_helper'
|
2
4
|
|
3
5
|
module Tire
|
@@ -170,7 +172,7 @@ module Tire
|
|
170
172
|
}
|
171
173
|
end
|
172
174
|
|
173
|
-
should "return the mapping" do
|
175
|
+
should "return the mapping as a Hash" do
|
174
176
|
json =<<-JSON
|
175
177
|
{
|
176
178
|
"dummy" : {
|
@@ -189,6 +191,33 @@ module Tire
|
|
189
191
|
assert_equal 2.0, @index.mapping['article']['properties']['title']['boost']
|
190
192
|
end
|
191
193
|
|
194
|
+
should "update the mapping" do
|
195
|
+
Configuration.client.expects(:put).returns(mock_response('{"ok":true,"acknowledged":true}', 200))
|
196
|
+
assert @index.mapping( 'document', { properties: { body: {type: 'string', analyzer: 'english'} } } )
|
197
|
+
end
|
198
|
+
|
199
|
+
should "fail to update the mapping when conflicts occur" do
|
200
|
+
Configuration.client.expects(:put).returns(mock_response('{"error":"MergeMappingException","status":400}', 400))
|
201
|
+
assert ! @index.mapping( 'document', { properties: { body: {type: 'string', analyzer: 'english'} } } )
|
202
|
+
end
|
203
|
+
|
204
|
+
should "raise an exception for the bang method" do
|
205
|
+
Configuration.client.expects(:put).returns(mock_response('{"error":"MergeMappingException","status":400}', 400))
|
206
|
+
assert_raise(RuntimeError) do
|
207
|
+
@index.mapping!("blah", {})
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
should "delete the mapping" do
|
212
|
+
Configuration.client.expects(:delete).returns(mock_response('{"ok":true}', 200))
|
213
|
+
assert @index.delete_mapping('document')
|
214
|
+
end
|
215
|
+
|
216
|
+
should "fail when deleting the mapping for non-existing type" do
|
217
|
+
Configuration.client.expects(:delete).returns(mock_response('{"error":"TypeMissingException[[dummy] type[document] missing]","status":404}', 400))
|
218
|
+
assert ! @index.delete_mapping('document')
|
219
|
+
end
|
220
|
+
|
192
221
|
end
|
193
222
|
|
194
223
|
context "settings" do
|
@@ -296,6 +325,16 @@ module Tire
|
|
296
325
|
@index.store( {:id => 123, :title => 'Test'}, {:routing => 'abc'} )
|
297
326
|
end
|
298
327
|
|
328
|
+
should "extract the replication type from options" do
|
329
|
+
Configuration.client.expects(:post).
|
330
|
+
with do |url, payload|
|
331
|
+
assert_equal "#{Configuration.url}/dummy/document/?replication=async", url
|
332
|
+
end.
|
333
|
+
returns(mock_response('{"ok":true,"_id":"test"}'))
|
334
|
+
|
335
|
+
@index.store({:title => 'Test'}, {:replication => 'async'})
|
336
|
+
end
|
337
|
+
|
299
338
|
context "document with ID" do
|
300
339
|
|
301
340
|
should "store Hash it under its ID property" do
|
@@ -331,6 +370,16 @@ module Tire
|
|
331
370
|
assert_equal 'c', @index.get_id_from_document(document_3)
|
332
371
|
end
|
333
372
|
|
373
|
+
should "escape the ID in URL" do
|
374
|
+
Configuration.client.expects(:post).with do |url, payload|
|
375
|
+
assert_equal "#{@index.url}/document/%C3%A4ccent", url
|
376
|
+
assert_equal 'äccent', MultiJson.load(payload)['id']
|
377
|
+
end.
|
378
|
+
returns(mock_response('{"ok":true,"_id":"äccent"}'))
|
379
|
+
|
380
|
+
@index.store :id => 'äccent'
|
381
|
+
end
|
382
|
+
|
334
383
|
end
|
335
384
|
|
336
385
|
end
|
@@ -389,6 +438,13 @@ module Tire
|
|
389
438
|
end
|
390
439
|
end
|
391
440
|
|
441
|
+
should "escape the ID in URL" do
|
442
|
+
Configuration.client.expects(:get).with("#{@index.url}/document/%C3%A4ccent").
|
443
|
+
returns(mock_response('{"_id":"äccent"}'))
|
444
|
+
|
445
|
+
@index.retrieve 'document', 'äccent'
|
446
|
+
end
|
447
|
+
|
392
448
|
should "properly encode document type" do
|
393
449
|
Configuration.client.expects(:get).with("#{@index.url}/my_namespace%2Fmy_model/id-1").
|
394
450
|
returns(mock_response('{"_id":"id-1","_version":1, "_source" : {"title":"Test"}}'))
|
@@ -467,6 +523,13 @@ module Tire
|
|
467
523
|
end
|
468
524
|
end
|
469
525
|
|
526
|
+
should "escape the ID in URL" do
|
527
|
+
Configuration.client.expects(:delete).with("#{@index.url}/document/%C3%A4ccent").
|
528
|
+
returns(mock_response('{"ok":true,"_id":"äccent"}'))
|
529
|
+
|
530
|
+
@index.remove 'document', 'äccent'
|
531
|
+
end
|
532
|
+
|
470
533
|
should "properly encode document type" do
|
471
534
|
Configuration.client.expects(:delete).with("#{@index.url}/my_namespace%2Fmy_model/id-1").
|
472
535
|
returns(mock_response('{"_id":"id-1","_version":1, "_source" : {"title":"Test"}}'))
|
@@ -523,6 +586,15 @@ module Tire
|
|
523
586
|
assert_raise(ArgumentError) { @index.update(nil, '123', :script => 'foobar') }
|
524
587
|
end
|
525
588
|
|
589
|
+
should "escape the ID in URL" do
|
590
|
+
Configuration.client.expects(:post).with do |url,payload|
|
591
|
+
assert_equal( "#{@index.url}/document/%C3%A4ccent/_update", url )
|
592
|
+
end.
|
593
|
+
returns(mock_response('{"ok":"true","_id":"äccent","_version":"2"}'))
|
594
|
+
|
595
|
+
assert @index.update('document', 'äccent', {:doc => {:foo => 'bar'}})
|
596
|
+
end
|
597
|
+
|
526
598
|
should "raise an error when no script or partial document is passed" do
|
527
599
|
assert_raise ArgumentError do
|
528
600
|
@index.update('article', "42", {:foo => 'bar'})
|
@@ -749,6 +821,12 @@ module Tire
|
|
749
821
|
@index.bulk :index, [ {:id => '1', :title => 'One'} ]
|
750
822
|
end
|
751
823
|
|
824
|
+
|
825
|
+
should "return immediately with empty collection" do
|
826
|
+
Configuration.client.expects(:post).never
|
827
|
+
@index.bulk_store []
|
828
|
+
end
|
829
|
+
|
752
830
|
end
|
753
831
|
|
754
832
|
context "when importing" do
|