tire 0.5.8 → 0.6.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.
- data/README.markdown +25 -3
- data/lib/tire.rb +1 -0
- data/lib/tire/dsl.rb +16 -7
- data/lib/tire/http/client.rb +4 -0
- data/lib/tire/http/clients/curb.rb +4 -0
- data/lib/tire/http/clients/faraday.rb +7 -3
- data/lib/tire/index.rb +23 -10
- data/lib/tire/model/import.rb +13 -4
- data/lib/tire/model/indexing.rb +2 -1
- data/lib/tire/results/collection.rb +10 -5
- data/lib/tire/results/item.rb +3 -0
- data/lib/tire/search/facet.rb +5 -5
- data/lib/tire/search/queries/custom_filters_score.rb +128 -0
- data/lib/tire/search/query.rb +8 -10
- data/lib/tire/tasks.rb +1 -11
- data/lib/tire/version.rb +13 -7
- data/test/integration/bulk_test.rb +13 -0
- data/test/integration/custom_filters_score_queries_test.rb +105 -0
- data/test/integration/dsl_search_test.rb +32 -0
- data/test/integration/facets_test.rb +47 -0
- data/test/integration/results_test.rb +31 -1
- data/test/unit/http_client_test.rb +8 -0
- data/test/unit/index_test.rb +37 -9
- data/test/unit/model_import_test.rb +17 -0
- data/test/unit/model_initialization_test.rb +43 -4
- data/test/unit/results_collection_test.rb +62 -0
- data/test/unit/results_item_test.rb +39 -1
- data/test/unit/search_facet_test.rb +15 -0
- data/test/unit/search_query_test.rb +129 -14
- data/test/unit/tire_test.rb +13 -0
- data/tire.gemspec +1 -0
- metadata +30 -10
- data/test/integration/text_query_test.rb +0 -39
@@ -67,6 +67,23 @@ module Tire
|
|
67
67
|
ImportModel.import :index => 'new_index'
|
68
68
|
end
|
69
69
|
|
70
|
+
context 'Strategy' do
|
71
|
+
class ::CustomImportStrategy
|
72
|
+
include Tire::Model::Import::Strategy::Base
|
73
|
+
end
|
74
|
+
|
75
|
+
should 'return explicitly specified strategy from predefined strategies' do
|
76
|
+
strategy = Tire::Model::Import::Strategy.from_class(ImportModel, :strategy => 'WillPaginate')
|
77
|
+
assert_equal strategy.class.name, 'Tire::Model::Import::Strategy::WillPaginate'
|
78
|
+
end
|
79
|
+
|
80
|
+
should 'return custom strategy class' do
|
81
|
+
strategy = Tire::Model::Import::Strategy.from_class(ImportModel, :strategy => 'CustomImportStrategy')
|
82
|
+
assert_equal strategy.class.name, 'CustomImportStrategy'
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
70
87
|
end
|
71
88
|
|
72
89
|
end
|
@@ -12,6 +12,11 @@ class ModelWithIncorrectMapping
|
|
12
12
|
end
|
13
13
|
end
|
14
14
|
|
15
|
+
class MyModelForIndexCreate
|
16
|
+
extend ActiveModel::Naming
|
17
|
+
include Tire::Model::Search
|
18
|
+
end
|
19
|
+
|
15
20
|
module Tire
|
16
21
|
module Model
|
17
22
|
|
@@ -19,10 +24,44 @@ module Tire
|
|
19
24
|
|
20
25
|
context "Model initialization" do
|
21
26
|
|
22
|
-
should "display a warning when creating the index fails" do
|
23
|
-
|
24
|
-
|
25
|
-
|
27
|
+
should "display a warning and not raise exception when creating the index fails" do
|
28
|
+
assert_nothing_raised do
|
29
|
+
STDERR.expects(:puts)
|
30
|
+
result = ModelWithIncorrectMapping.create_elasticsearch_index
|
31
|
+
assert ! result, result.inspect
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
should "re-raise non-connection related exceptions" do
|
36
|
+
Tire::Index.any_instance.expects(:exists?).raises(ZeroDivisionError)
|
37
|
+
|
38
|
+
assert_raise(ZeroDivisionError) do
|
39
|
+
result = MyModelForIndexCreate.create_elasticsearch_index
|
40
|
+
assert ! result, result.inspect
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
unless defined?(Curl)
|
45
|
+
|
46
|
+
should "display a warning and not raise exception when cannot connect to Elasticsearch (default client)" do
|
47
|
+
Tire::Index.any_instance.expects(:exists?).raises(Errno::ECONNREFUSED)
|
48
|
+
assert_nothing_raised do
|
49
|
+
STDERR.expects(:puts)
|
50
|
+
result = MyModelForIndexCreate.create_elasticsearch_index
|
51
|
+
assert ! result, result.inspect
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
else
|
56
|
+
should "display a warning and not raise exception when cannot connect to Elasticsearch (Curb client)" do
|
57
|
+
Tire::Index.any_instance.expects(:exists?).raises(::Curl::Err::HostResolutionError)
|
58
|
+
assert_nothing_raised do
|
59
|
+
STDERR.expects(:puts)
|
60
|
+
result = MyModelForIndexCreate.create_elasticsearch_index
|
61
|
+
assert ! result, result.inspect
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
26
65
|
end
|
27
66
|
|
28
67
|
end
|
@@ -79,6 +79,11 @@ module Tire
|
|
79
79
|
assert_equal 1.0, collection.max_score
|
80
80
|
end
|
81
81
|
|
82
|
+
should "return an array" do
|
83
|
+
collection = Results::Collection.new(@default_response)
|
84
|
+
assert_instance_of Array, collection.to_ary
|
85
|
+
end
|
86
|
+
|
82
87
|
context "serialization" do
|
83
88
|
|
84
89
|
should "be serialized to JSON" do
|
@@ -215,6 +220,32 @@ module Tire
|
|
215
220
|
|
216
221
|
end
|
217
222
|
|
223
|
+
context "using fields when also returning the _source" do
|
224
|
+
setup do
|
225
|
+
Configuration.reset
|
226
|
+
@default_response = { 'hits' => { 'hits' =>
|
227
|
+
[ { '_id' => 1, '_score' => 0.5, '_index' => 'testing', '_type' => 'article',
|
228
|
+
'_source' => {
|
229
|
+
'title' => 'Knee Deep in JSON'
|
230
|
+
},
|
231
|
+
'fields' => {
|
232
|
+
'_parent' => '4f99f98ea2b279ec3d002522'
|
233
|
+
}
|
234
|
+
}
|
235
|
+
] } }
|
236
|
+
collection = Results::Collection.new(@default_response)
|
237
|
+
@item = collection.first
|
238
|
+
end
|
239
|
+
|
240
|
+
should "return an individual field" do
|
241
|
+
assert_equal '4f99f98ea2b279ec3d002522', @item._parent
|
242
|
+
end
|
243
|
+
|
244
|
+
should "return fields from the _source as well" do
|
245
|
+
assert_equal 'Knee Deep in JSON', @item.title
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
218
249
|
context "returning results with hits" do
|
219
250
|
should "yield the Item result and the raw hit" do
|
220
251
|
response = { 'hits' => { 'hits' => [ { '_id' => 1, '_score' => 0.5, '_index' => 'testing', '_type' => 'article', '_source' => { :title => 'Test', :body => 'Lorem' } } ] } }
|
@@ -362,7 +393,38 @@ module Tire
|
|
362
393
|
assert @collection.results.empty?, 'Collection results should be empty'
|
363
394
|
assert_equal 0, @collection.size
|
364
395
|
end
|
396
|
+
end
|
397
|
+
|
398
|
+
context "with ActiveModel::Serializers" do
|
399
|
+
setup do
|
400
|
+
require 'active_model_serializers'
|
401
|
+
|
402
|
+
Tire::Results::Collection.send :include, ActiveModel::ArraySerializerSupport
|
365
403
|
|
404
|
+
class ::MyItemWithSerializer < Tire::Results::Item
|
405
|
+
include ActiveModel::SerializerSupport
|
406
|
+
|
407
|
+
def active_model_serializer
|
408
|
+
::MyItemSerializer
|
409
|
+
end
|
410
|
+
end
|
411
|
+
|
412
|
+
class ::MyItemSerializer < ActiveModel::Serializer
|
413
|
+
attribute :title, :key => :name
|
414
|
+
attribute :author, :key => :owner
|
415
|
+
end
|
416
|
+
end
|
417
|
+
|
418
|
+
should "be serializable" do
|
419
|
+
assert_nothing_raised do
|
420
|
+
collection = Results::Collection.new(@default_response, :wrapper => ::MyItemWithSerializer)
|
421
|
+
serializer = collection.active_model_serializer.new(collection)
|
422
|
+
|
423
|
+
hash = serializer.as_json.first
|
424
|
+
assert_equal 'Test', hash[:name]
|
425
|
+
assert_equal 'John', hash[:owner]
|
426
|
+
end
|
427
|
+
end
|
366
428
|
end
|
367
429
|
|
368
430
|
end
|
@@ -20,7 +20,7 @@ module Tire
|
|
20
20
|
:awards => { :best_fiction => { :year => '1925' } },
|
21
21
|
:reviews => [ { :stars => 5, :comment => 'great' },
|
22
22
|
{ :stars => 3, :comment => 'decent' } ]
|
23
|
-
|
23
|
+
|
24
24
|
end
|
25
25
|
|
26
26
|
should "be initialized with a Hash or Hash like object" do
|
@@ -63,6 +63,15 @@ module Tire
|
|
63
63
|
assert_equal 'Kafka', @document[:author][:name]
|
64
64
|
end
|
65
65
|
|
66
|
+
should "retrieve simple values from read_attribute_for_serialization" do
|
67
|
+
assert_equal 'Test', @document.read_attribute_for_serialization(:title)
|
68
|
+
end
|
69
|
+
|
70
|
+
should "retrieve hash values from read_attribute_for_serialization" do
|
71
|
+
assert_equal 'Kafka', @document.read_attribute_for_serialization(:author)[:name]
|
72
|
+
end
|
73
|
+
|
74
|
+
|
66
75
|
should "allow to retrieve value by methods" do
|
67
76
|
assert_not_nil @document.title
|
68
77
|
assert_equal 'Test', @document.title
|
@@ -171,6 +180,35 @@ module Tire
|
|
171
180
|
|
172
181
|
end
|
173
182
|
|
183
|
+
context "with ActiveModel::Serializers" do
|
184
|
+
setup do
|
185
|
+
require 'active_model_serializers'
|
186
|
+
|
187
|
+
class ::MyItemWithSerializer < Tire::Results::Item
|
188
|
+
include ActiveModel::SerializerSupport
|
189
|
+
end
|
190
|
+
class ::MyItemSerializer < ActiveModel::Serializer
|
191
|
+
attribute :tags
|
192
|
+
attribute :title, :key => :name
|
193
|
+
|
194
|
+
def tags
|
195
|
+
object.tags.join('-')
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
should "be serializable" do
|
201
|
+
assert_nothing_raised do
|
202
|
+
doc = ::MyItemWithSerializer.new :title => 'Test', :tags => ['foo', 'bar']
|
203
|
+
doc_serializer = ::MyItemSerializer.new(doc)
|
204
|
+
|
205
|
+
hash = doc_serializer.as_json
|
206
|
+
assert_equal 'Test', hash[:my_item][:name]
|
207
|
+
assert_equal 'foo-bar', hash[:my_item][:tags]
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
174
212
|
end
|
175
213
|
|
176
214
|
end
|
@@ -168,6 +168,21 @@ module Tire::Search
|
|
168
168
|
end
|
169
169
|
end
|
170
170
|
|
171
|
+
context "geo_distance facet" do
|
172
|
+
should "encode facet options" do
|
173
|
+
f = Facet.new('geo_distance') { geo_distance :location, {:lat => 50, :lon => 9}, [{:to => 1}] }
|
174
|
+
assert_equal({:geo_distance => {:geo_distance => {:location => {:lat => 50, :lon => 9}, :ranges => [{:to => 1}]}}}.to_json,
|
175
|
+
f.to_json)
|
176
|
+
end
|
177
|
+
|
178
|
+
should "encode custom options" do
|
179
|
+
f = Facet.new('geo_distance') { geo_distance :location, {:lat => 50, :lon => 9}, [{:to => 1}],
|
180
|
+
:unit => 'km', :value_script => 'doc["field"].value'}
|
181
|
+
assert_equal({:geo_distance => {:geo_distance => {:location => {:lat => 50, :lon => 9}, :ranges => [{:to => 1}],
|
182
|
+
:unit => "km", :value_script => 'doc["field"].value'}}}.to_json, f.to_json)
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
171
186
|
context "filter facet" do
|
172
187
|
|
173
188
|
should "encode facet options" do
|
@@ -68,17 +68,6 @@ module Tire::Search
|
|
68
68
|
end
|
69
69
|
end
|
70
70
|
|
71
|
-
context "Text query" do
|
72
|
-
should "allow search with a text search" do
|
73
|
-
assert_equal( { :text => {'field' => {:query => 'foo'}}}, Query.new.text('field', 'foo'))
|
74
|
-
end
|
75
|
-
|
76
|
-
should "allow search with a different operator for text search" do
|
77
|
-
assert_equal( { :text => {'field' => {:query => 'foo', :operator => 'and'}}},
|
78
|
-
Query.new.text('field', 'foo', :operator => 'and'))
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
71
|
context "Query String query" do
|
83
72
|
should "allow search with a query string" do
|
84
73
|
assert_equal( { :query_string => { :query => 'title:foo' } },
|
@@ -121,6 +110,121 @@ module Tire::Search
|
|
121
110
|
end
|
122
111
|
end
|
123
112
|
|
113
|
+
context 'CustomFiltersScoreQuery' do
|
114
|
+
should "not raise an error when no block is given" do
|
115
|
+
assert_nothing_raised { Query.new.custom_filters_score }
|
116
|
+
end
|
117
|
+
|
118
|
+
should "provides a default filter if no filter is given" do
|
119
|
+
query = Query.new.custom_filters_score do
|
120
|
+
query { term :foo, 'bar' }
|
121
|
+
end
|
122
|
+
|
123
|
+
f = query[:custom_filters_score]
|
124
|
+
|
125
|
+
assert_equal( { :term => { :foo => { :term => 'bar' } } }, f[:query].to_hash )
|
126
|
+
assert_equal( { :match_all => {} }, f[:filters].first[:filter])
|
127
|
+
assert_equal( 1.0, f[:filters].first[:boost])
|
128
|
+
end
|
129
|
+
|
130
|
+
should "properly encode filter with boost" do
|
131
|
+
query = Query.new.custom_filters_score do
|
132
|
+
query { term :foo, 'bar' }
|
133
|
+
filter do
|
134
|
+
filter :terms, :tags => ['ruby']
|
135
|
+
boost 2.0
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
f = query[:custom_filters_score]
|
140
|
+
|
141
|
+
assert_equal( { :term => { :foo => { :term => 'bar' } } }, f[:query].to_hash )
|
142
|
+
assert_equal( { :tags => ['ruby'] }, f[:filters].first[:filter][:terms])
|
143
|
+
assert_equal( 2.0, f[:filters].first[:boost])
|
144
|
+
end
|
145
|
+
|
146
|
+
should "properly encode filter with script" do
|
147
|
+
query = Query.new.custom_filters_score do
|
148
|
+
query { term :foo, 'bar' }
|
149
|
+
filter do
|
150
|
+
filter :terms, :tags => ['ruby']
|
151
|
+
script '2.0'
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
f = query[:custom_filters_score]
|
156
|
+
|
157
|
+
assert_equal( { :term => { :foo => { :term => 'bar' } } }, f[:query].to_hash )
|
158
|
+
assert_equal( { :tags => ['ruby'] }, f[:filters].first[:filter][:terms])
|
159
|
+
assert_equal( '2.0', f[:filters].first[:script])
|
160
|
+
end
|
161
|
+
|
162
|
+
should "properly encode multiple filters" do
|
163
|
+
query = Query.new.custom_filters_score do
|
164
|
+
query { term :foo, 'bar' }
|
165
|
+
filter do
|
166
|
+
filter :terms, :tags => ['ruby']
|
167
|
+
boost 2.0
|
168
|
+
end
|
169
|
+
filter do
|
170
|
+
filter :terms, :tags => ['python']
|
171
|
+
script '2.0'
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
f = query[:custom_filters_score]
|
176
|
+
|
177
|
+
assert_equal( { :term => { :foo => { :term => 'bar' } } }, f[:query].to_hash )
|
178
|
+
assert_equal( { :tags => ['ruby'] }, f[:filters].first[:filter][:terms])
|
179
|
+
assert_equal( 2.0, f[:filters].first[:boost])
|
180
|
+
assert_equal( { :tags => ['python'] }, f[:filters].last[:filter][:terms])
|
181
|
+
assert_equal( '2.0', f[:filters].last[:script])
|
182
|
+
end
|
183
|
+
|
184
|
+
should "allow setting the score_mode" do
|
185
|
+
query = Query.new.custom_filters_score do
|
186
|
+
query { term :foo, 'bar' }
|
187
|
+
score_mode 'total'
|
188
|
+
end
|
189
|
+
|
190
|
+
f = query[:custom_filters_score]
|
191
|
+
|
192
|
+
assert_equal( { :term => { :foo => { :term => 'bar' } } }, f[:query].to_hash )
|
193
|
+
assert_equal( 'total', f[:score_mode])
|
194
|
+
end
|
195
|
+
|
196
|
+
should "allow setting params" do
|
197
|
+
query = Query.new.custom_filters_score do
|
198
|
+
query { term :foo, 'bar' }
|
199
|
+
params :a => 'b'
|
200
|
+
end
|
201
|
+
|
202
|
+
f = query[:custom_filters_score]
|
203
|
+
|
204
|
+
assert_equal( { :term => { :foo => { :term => 'bar' } } }, f[:query].to_hash )
|
205
|
+
assert_equal( { :a => 'b' }, f[:params] )
|
206
|
+
end
|
207
|
+
|
208
|
+
should "allow using script parameters" do
|
209
|
+
score_script = "foo * 2"
|
210
|
+
|
211
|
+
query = Query.new.custom_filters_score do
|
212
|
+
query { string 'foo' }
|
213
|
+
|
214
|
+
params :foo => 42
|
215
|
+
|
216
|
+
filter do
|
217
|
+
filter :exists, :field => 'date'
|
218
|
+
script score_script
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
f = query[:custom_filters_score]
|
223
|
+
|
224
|
+
assert_equal 42, f[:params][:foo]
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
124
228
|
context "All query" do
|
125
229
|
should "search for all documents" do
|
126
230
|
assert_equal( { :match_all => { } }, Query.new.all )
|
@@ -133,9 +237,17 @@ module Tire::Search
|
|
133
237
|
|
134
238
|
context "IDs query" do
|
135
239
|
should "search for documents by IDs" do
|
240
|
+
assert_equal( { :ids => { :values => [1, 2] } },
|
241
|
+
Query.new.ids([1, 2]) )
|
242
|
+
end
|
243
|
+
should "search for documents by IDs and type" do
|
136
244
|
assert_equal( { :ids => { :values => [1, 2], :type => 'foo' } },
|
137
245
|
Query.new.ids([1, 2], 'foo') )
|
138
246
|
end
|
247
|
+
should "convert argument to Array" do
|
248
|
+
assert_equal( { :ids => { :values => [1] } },
|
249
|
+
Query.new.ids(1) )
|
250
|
+
end
|
139
251
|
end
|
140
252
|
|
141
253
|
context "FuzzyQuery" do
|
@@ -430,9 +542,12 @@ module Tire::Search
|
|
430
542
|
Query.new.constant_score { query { term :attr, 'foo' } } )
|
431
543
|
end
|
432
544
|
|
433
|
-
should "wrap
|
434
|
-
assert_equal( { :constant_score => {:filter => { :term => { :attr => 'foo' } } } },
|
435
|
-
Query.new.constant_score
|
545
|
+
should "wrap multiple filters" do
|
546
|
+
assert_equal( { :constant_score => {:filter => {:and => [ { :term => { :attr => 'foo' } }, { :term => { :attr => 'bar' } } ] } } },
|
547
|
+
Query.new.constant_score do
|
548
|
+
filter :term, :attr => 'foo'
|
549
|
+
filter :term, :attr => 'bar'
|
550
|
+
end )
|
436
551
|
end
|
437
552
|
|
438
553
|
should "wrap the boost" do
|
data/test/unit/tire_test.rb
CHANGED
@@ -41,6 +41,19 @@ module Tire
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
+
should "convert params objects to hash" do
|
45
|
+
class ::MyParamsObject;
|
46
|
+
def initialize(hash)
|
47
|
+
@hash = hash
|
48
|
+
end
|
49
|
+
def to_hash
|
50
|
+
@hash
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
Tire.search 'dummy', ::MyParamsObject.new(:foo => 'bar')
|
55
|
+
end
|
56
|
+
|
44
57
|
should "extract URL parameters from options" do
|
45
58
|
payload = { :query => { :match => { :foo => 'bar' } } }
|
46
59
|
|
data/tire.gemspec
CHANGED
@@ -41,6 +41,7 @@ Gem::Specification.new do |s|
|
|
41
41
|
s.add_development_dependency "mocha", "~> 0.13"
|
42
42
|
s.add_development_dependency "minitest", "~> 2.12"
|
43
43
|
s.add_development_dependency "activerecord", ">= 3.0"
|
44
|
+
s.add_development_dependency "active_model_serializers"
|
44
45
|
s.add_development_dependency "mongoid", "~> 2.2"
|
45
46
|
s.add_development_dependency "redis-persistence"
|
46
47
|
s.add_development_dependency "faraday"
|