tire 0.5.4 → 0.5.5
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.
- 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
|