tire 0.4.3 → 0.5.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/.gitignore +1 -1
- data/.yardopts +1 -0
- data/README.markdown +2 -2
- data/examples/rails-application-template.rb +20 -6
- data/lib/tire.rb +2 -0
- data/lib/tire/alias.rb +1 -1
- data/lib/tire/configuration.rb +8 -0
- data/lib/tire/dsl.rb +69 -2
- data/lib/tire/index.rb +33 -20
- data/lib/tire/model/indexing.rb +7 -1
- data/lib/tire/model/persistence.rb +7 -4
- data/lib/tire/model/persistence/attributes.rb +1 -1
- data/lib/tire/model/persistence/finders.rb +4 -16
- data/lib/tire/model/search.rb +21 -8
- data/lib/tire/multi_search.rb +263 -0
- data/lib/tire/results/collection.rb +78 -49
- data/lib/tire/results/item.rb +6 -3
- data/lib/tire/results/pagination.rb +15 -1
- data/lib/tire/rubyext/ruby_1_8.rb +1 -7
- data/lib/tire/rubyext/uri_escape.rb +74 -0
- data/lib/tire/search.rb +33 -11
- data/lib/tire/search/facet.rb +8 -3
- data/lib/tire/search/filter.rb +1 -1
- data/lib/tire/search/highlight.rb +1 -1
- data/lib/tire/search/queries/match.rb +40 -0
- data/lib/tire/search/query.rb +42 -6
- data/lib/tire/search/scan.rb +1 -1
- data/lib/tire/search/script_field.rb +1 -1
- data/lib/tire/search/sort.rb +1 -1
- data/lib/tire/tasks.rb +17 -14
- data/lib/tire/version.rb +26 -8
- data/test/integration/active_record_searchable_test.rb +248 -129
- data/test/integration/boosting_queries_test.rb +32 -0
- data/test/integration/custom_score_queries_test.rb +1 -0
- data/test/integration/dsl_search_test.rb +9 -1
- data/test/integration/facets_test.rb +19 -6
- data/test/integration/match_query_test.rb +79 -0
- data/test/integration/multi_search_test.rb +114 -0
- data/test/integration/persistent_model_test.rb +58 -0
- data/test/models/article.rb +1 -1
- data/test/models/persistent_article_in_index.rb +16 -0
- data/test/models/persistent_article_with_defaults.rb +4 -3
- data/test/test_helper.rb +3 -1
- data/test/unit/configuration_test.rb +10 -0
- data/test/unit/index_test.rb +69 -27
- data/test/unit/model_initialization_test.rb +31 -0
- data/test/unit/model_persistence_test.rb +21 -7
- data/test/unit/model_search_test.rb +56 -5
- data/test/unit/multi_search_test.rb +304 -0
- data/test/unit/results_collection_test.rb +42 -2
- data/test/unit/results_item_test.rb +4 -0
- data/test/unit/search_facet_test.rb +35 -11
- data/test/unit/search_query_test.rb +96 -0
- data/test/unit/search_test.rb +60 -3
- data/test/unit/tire_test.rb +14 -0
- data/tire.gemspec +0 -1
- metadata +75 -44
@@ -1,7 +1 @@
|
|
1
|
-
require '
|
2
|
-
|
3
|
-
# Require URI escape/unescape compatibility layer from Rack
|
4
|
-
#
|
5
|
-
# See <http://www.ruby-doc.org/stdlib-1.9.3/libdoc/uri/rdoc/URI.html#method-c-encode_www_form_component>
|
6
|
-
#
|
7
|
-
require 'rack/backports/uri/common_18'
|
1
|
+
require 'tire/rubyext/uri_escape'
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# Steal the URI escape/unescape compatibility layer from Rack
|
2
|
+
#
|
3
|
+
# See <http://www.ruby-doc.org/stdlib-1.9.3/libdoc/uri/rdoc/URI.html#method-c-encode_www_form_component>
|
4
|
+
|
5
|
+
# :stopdoc:
|
6
|
+
|
7
|
+
# Stolen from ruby core's uri/common.rb, with modifications to support 1.8.x
|
8
|
+
#
|
9
|
+
# https://github.com/ruby/ruby/blob/trunk/lib/uri/common.rb
|
10
|
+
#
|
11
|
+
#
|
12
|
+
|
13
|
+
module URI
|
14
|
+
TBLENCWWWCOMP_ = {} # :nodoc:
|
15
|
+
TBLDECWWWCOMP_ = {} # :nodoc:
|
16
|
+
|
17
|
+
# Encode given +s+ to URL-encoded form data.
|
18
|
+
#
|
19
|
+
# This method doesn't convert *, -, ., 0-9, A-Z, _, a-z, but does convert SP
|
20
|
+
# (ASCII space) to + and converts others to %XX.
|
21
|
+
#
|
22
|
+
# This is an implementation of
|
23
|
+
# http://www.w3.org/TR/html5/forms.html#url-encoded-form-data
|
24
|
+
#
|
25
|
+
# See URI.decode_www_form_component, URI.encode_www_form
|
26
|
+
def self.encode_www_form_component(s)
|
27
|
+
str = s.to_s
|
28
|
+
if RUBY_VERSION < "1.9" && $KCODE =~ /u/i
|
29
|
+
str.gsub(/([^ a-zA-Z0-9_.-]+)/) do
|
30
|
+
'%' + $1.unpack('H2' * Rack::Utils.bytesize($1)).join('%').upcase
|
31
|
+
end.tr(' ', '+')
|
32
|
+
else
|
33
|
+
if TBLENCWWWCOMP_.empty?
|
34
|
+
tbl = {}
|
35
|
+
256.times do |i|
|
36
|
+
tbl[i.chr] = '%%%02X' % i
|
37
|
+
end
|
38
|
+
tbl[' '] = '+'
|
39
|
+
begin
|
40
|
+
TBLENCWWWCOMP_.replace(tbl)
|
41
|
+
TBLENCWWWCOMP_.freeze
|
42
|
+
rescue
|
43
|
+
end
|
44
|
+
end
|
45
|
+
str.gsub(/[^*\-.0-9A-Z_a-z]/) {|m| TBLENCWWWCOMP_[m]}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Decode given +str+ of URL-encoded form data.
|
50
|
+
#
|
51
|
+
# This decods + to SP.
|
52
|
+
#
|
53
|
+
# See URI.encode_www_form_component, URI.decode_www_form
|
54
|
+
def self.decode_www_form_component(str, enc=nil)
|
55
|
+
if TBLDECWWWCOMP_.empty?
|
56
|
+
tbl = {}
|
57
|
+
256.times do |i|
|
58
|
+
h, l = i>>4, i&15
|
59
|
+
tbl['%%%X%X' % [h, l]] = i.chr
|
60
|
+
tbl['%%%x%X' % [h, l]] = i.chr
|
61
|
+
tbl['%%%X%x' % [h, l]] = i.chr
|
62
|
+
tbl['%%%x%x' % [h, l]] = i.chr
|
63
|
+
end
|
64
|
+
tbl['+'] = ' '
|
65
|
+
begin
|
66
|
+
TBLDECWWWCOMP_.replace(tbl)
|
67
|
+
TBLDECWWWCOMP_.freeze
|
68
|
+
rescue
|
69
|
+
end
|
70
|
+
end
|
71
|
+
raise ArgumentError, "invalid %-encoding (#{str})" unless /\A(?:%[0-9a-fA-F]{2}|[^%])*\z/ =~ str
|
72
|
+
str.gsub(/\+|%[0-9a-fA-F]{2}/) {|m| TBLDECWWWCOMP_[m]}
|
73
|
+
end
|
74
|
+
end
|
data/lib/tire/search.rb
CHANGED
@@ -4,7 +4,7 @@ module Tire
|
|
4
4
|
|
5
5
|
class Search
|
6
6
|
|
7
|
-
attr_reader :indices, :query, :facets, :filters, :options, :explain, :script_fields
|
7
|
+
attr_reader :indices, :types, :query, :facets, :filters, :options, :explain, :script_fields
|
8
8
|
|
9
9
|
def initialize(indices=nil, options={}, &block)
|
10
10
|
if indices.is_a?(Hash)
|
@@ -48,7 +48,8 @@ module Tire
|
|
48
48
|
end
|
49
49
|
|
50
50
|
def params
|
51
|
-
|
51
|
+
options = @options.except(:wrapper)
|
52
|
+
options.empty? ? '' : '?' + options.to_param
|
52
53
|
end
|
53
54
|
|
54
55
|
def query(&block)
|
@@ -106,6 +107,11 @@ module Tire
|
|
106
107
|
self
|
107
108
|
end
|
108
109
|
|
110
|
+
def partial_field(name, options)
|
111
|
+
@partial_fields ||= {}
|
112
|
+
@partial_fields[name] = options
|
113
|
+
end
|
114
|
+
|
109
115
|
def explain(value)
|
110
116
|
@explain = value
|
111
117
|
self
|
@@ -116,6 +122,16 @@ module Tire
|
|
116
122
|
self
|
117
123
|
end
|
118
124
|
|
125
|
+
def min_score(value)
|
126
|
+
@min_score = value
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
def track_scores(value)
|
131
|
+
@track_scores = value
|
132
|
+
self
|
133
|
+
end
|
134
|
+
|
119
135
|
def perform
|
120
136
|
@response = Configuration.client.get(self.url + self.params, self.to_json)
|
121
137
|
if @response.failure?
|
@@ -130,11 +146,11 @@ module Tire
|
|
130
146
|
end
|
131
147
|
|
132
148
|
def to_curl
|
133
|
-
%Q|curl -X GET
|
149
|
+
%Q|curl -X GET '#{url}#{params.empty? ? '?' : params.to_s + '&'}pretty' -d '#{to_json}'|
|
134
150
|
end
|
135
151
|
|
136
152
|
def to_hash
|
137
|
-
@options
|
153
|
+
@options[:payload] || begin
|
138
154
|
request = {}
|
139
155
|
request.update( { :indices_boost => @indices_boost } ) if @indices_boost
|
140
156
|
request.update( { :query => @query.to_hash } ) if @query
|
@@ -146,33 +162,39 @@ module Tire
|
|
146
162
|
request.update( { :size => @size } ) if @size
|
147
163
|
request.update( { :from => @from } ) if @from
|
148
164
|
request.update( { :fields => @fields } ) if @fields
|
165
|
+
request.update( { :partial_fields => @partial_fields } ) if @partial_fields
|
149
166
|
request.update( { :script_fields => @script_fields } ) if @script_fields
|
150
167
|
request.update( { :version => @version } ) if @version
|
151
168
|
request.update( { :explain => @explain } ) if @explain
|
169
|
+
request.update( { :min_score => @min_score } ) if @min_score
|
170
|
+
request.update( { :track_scores => @track_scores } ) if @track_scores
|
152
171
|
request
|
153
172
|
end
|
154
173
|
end
|
155
174
|
|
156
|
-
def to_json
|
175
|
+
def to_json(options={})
|
157
176
|
payload = to_hash
|
158
177
|
# TODO: Remove when deprecated interface is removed
|
159
|
-
payload.is_a?(String)
|
178
|
+
if payload.is_a?(String)
|
179
|
+
payload
|
180
|
+
else
|
181
|
+
MultiJson.encode(payload, :pretty => Configuration.pretty)
|
182
|
+
end
|
160
183
|
end
|
161
184
|
|
162
|
-
def logged(
|
185
|
+
def logged(endpoint='_search')
|
163
186
|
if Configuration.logger
|
164
187
|
|
165
|
-
Configuration.logger.log_request
|
188
|
+
Configuration.logger.log_request endpoint, indices, to_curl
|
166
189
|
|
167
190
|
took = @json['took'] rescue nil
|
168
191
|
code = @response.code rescue nil
|
169
192
|
|
170
193
|
if Configuration.logger.level.to_s == 'debug'
|
171
|
-
# FIXME: Depends on RestClient implementation
|
172
194
|
body = if @json
|
173
|
-
|
195
|
+
MultiJson.encode( @json, :pretty => Configuration.pretty)
|
174
196
|
else
|
175
|
-
@response.body rescue
|
197
|
+
MultiJson.encode( MultiJson.load(@response.body), :pretty => Configuration.pretty) rescue ''
|
176
198
|
end
|
177
199
|
else
|
178
200
|
body = ''
|
data/lib/tire/search/facet.rb
CHANGED
@@ -55,12 +55,17 @@ module Tire
|
|
55
55
|
@value = { :query => Query.new(&block).to_hash }
|
56
56
|
end
|
57
57
|
|
58
|
-
def filter(
|
59
|
-
@value = { :filter =>
|
58
|
+
def filter(type, options={})
|
59
|
+
@value = { :filter => Filter.new(type, options) }
|
60
60
|
self
|
61
61
|
end
|
62
62
|
|
63
|
-
def
|
63
|
+
def facet_filter(type, *options)
|
64
|
+
@value[:facet_filter] = Filter.new(type, *options).to_hash
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
def to_json(options={})
|
64
69
|
to_hash.to_json
|
65
70
|
end
|
66
71
|
|
data/lib/tire/search/filter.rb
CHANGED
@@ -0,0 +1,40 @@
|
|
1
|
+
module Tire
|
2
|
+
module Search
|
3
|
+
class Query
|
4
|
+
|
5
|
+
def match(field, value, options={})
|
6
|
+
if @value.empty?
|
7
|
+
@value = MatchQuery.new(field, value, options).to_hash
|
8
|
+
else
|
9
|
+
MatchQuery.add(self, field, value, options)
|
10
|
+
end
|
11
|
+
@value
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class MatchQuery
|
16
|
+
def initialize(field, value, options={})
|
17
|
+
query_options = { :query => value }.merge(options)
|
18
|
+
|
19
|
+
if field.is_a?(Array)
|
20
|
+
@value = { :multi_match => query_options.merge( :fields => field ) }
|
21
|
+
else
|
22
|
+
@value = { :match => { field => query_options } }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.add(query, field, value, options={})
|
27
|
+
unless query.value[:bool]
|
28
|
+
original_value = query.value.dup
|
29
|
+
query.value = { :bool => {} }
|
30
|
+
(query.value[:bool][:must] ||= []) << original_value
|
31
|
+
end
|
32
|
+
query.value[:bool][:must] << MatchQuery.new(field, value, options).to_hash
|
33
|
+
end
|
34
|
+
|
35
|
+
def to_hash
|
36
|
+
@value
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
data/lib/tire/search/query.rb
CHANGED
@@ -2,13 +2,19 @@ module Tire
|
|
2
2
|
module Search
|
3
3
|
|
4
4
|
class Query
|
5
|
+
attr_accessor :value
|
6
|
+
|
5
7
|
def initialize(&block)
|
6
8
|
@value = {}
|
7
9
|
block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
|
8
10
|
end
|
9
11
|
|
10
12
|
def term(field, value, options={})
|
11
|
-
query =
|
13
|
+
query = if value.is_a?(Hash)
|
14
|
+
{ field => value.to_hash }
|
15
|
+
else
|
16
|
+
{ field => { :term => value }.update(options) }
|
17
|
+
end
|
12
18
|
@value = { :term => query }
|
13
19
|
end
|
14
20
|
|
@@ -23,6 +29,7 @@ module Tire
|
|
23
29
|
end
|
24
30
|
|
25
31
|
def text(field, value, options={})
|
32
|
+
Tire.warn "The 'text' query has been deprecated, please use a 'match' query."
|
26
33
|
query_options = { :query => value }.update(options)
|
27
34
|
@value = { :text => { field => query_options } }
|
28
35
|
@value
|
@@ -75,8 +82,8 @@ module Tire
|
|
75
82
|
@value
|
76
83
|
end
|
77
84
|
|
78
|
-
def all
|
79
|
-
@value = { :match_all =>
|
85
|
+
def all(options = {})
|
86
|
+
@value = { :match_all => options }
|
80
87
|
@value
|
81
88
|
end
|
82
89
|
|
@@ -84,11 +91,18 @@ module Tire
|
|
84
91
|
@value = { :ids => { :values => values, :type => type } }
|
85
92
|
end
|
86
93
|
|
94
|
+
def boosting(options={}, &block)
|
95
|
+
@boosting ||= BoostingQuery.new(options)
|
96
|
+
block.arity < 1 ? @boosting.instance_eval(&block) : block.call(@boosting) if block_given?
|
97
|
+
@value[:boosting] = @boosting.to_hash
|
98
|
+
@value
|
99
|
+
end
|
100
|
+
|
87
101
|
def to_hash
|
88
102
|
@value
|
89
103
|
end
|
90
104
|
|
91
|
-
def to_json
|
105
|
+
def to_json(options={})
|
92
106
|
to_hash.to_json
|
93
107
|
end
|
94
108
|
|
@@ -156,7 +170,7 @@ module Tire
|
|
156
170
|
@value
|
157
171
|
end
|
158
172
|
|
159
|
-
def to_json
|
173
|
+
def to_json(options={})
|
160
174
|
to_hash.to_json
|
161
175
|
end
|
162
176
|
end
|
@@ -177,10 +191,32 @@ module Tire
|
|
177
191
|
@value.update(@options)
|
178
192
|
end
|
179
193
|
|
180
|
-
def to_json
|
194
|
+
def to_json(options={})
|
181
195
|
to_hash.to_json
|
182
196
|
end
|
183
197
|
end
|
184
198
|
|
199
|
+
class BoostingQuery
|
200
|
+
def initialize(options={}, &block)
|
201
|
+
@options = options
|
202
|
+
@value = {}
|
203
|
+
block.arity < 1 ? self.instance_eval(&block) : block.call(self) if block_given?
|
204
|
+
end
|
205
|
+
|
206
|
+
def positive(&block)
|
207
|
+
(@value[:positive] ||= []) << Query.new(&block).to_hash
|
208
|
+
@value
|
209
|
+
end
|
210
|
+
|
211
|
+
def negative(&block)
|
212
|
+
(@value[:negative] ||= []) << Query.new(&block).to_hash
|
213
|
+
@value
|
214
|
+
end
|
215
|
+
|
216
|
+
def to_hash
|
217
|
+
@value.update(@options)
|
218
|
+
end
|
219
|
+
end
|
220
|
+
|
185
221
|
end
|
186
222
|
end
|
data/lib/tire/search/scan.rb
CHANGED
@@ -94,7 +94,7 @@ module Tire
|
|
94
94
|
end
|
95
95
|
|
96
96
|
def to_a; results; end; alias :to_ary :to_a
|
97
|
-
def to_curl; %Q|curl -X GET
|
97
|
+
def to_curl; %Q|curl -X GET '#{url}?pretty' -d '#{@scroll_id}'|; end
|
98
98
|
|
99
99
|
def __logged(error=nil)
|
100
100
|
if Configuration.logger
|
data/lib/tire/search/sort.rb
CHANGED
data/lib/tire/tasks.rb
CHANGED
@@ -3,7 +3,7 @@ require 'benchmark'
|
|
3
3
|
|
4
4
|
namespace :tire do
|
5
5
|
|
6
|
-
|
6
|
+
full_comment_import = <<-DESC.gsub(/ /, '')
|
7
7
|
Import data from your model using paginate: rake environment tire:import CLASS='MyModel'.
|
8
8
|
|
9
9
|
Pass params for the `paginate` method:
|
@@ -15,7 +15,7 @@ namespace :tire do
|
|
15
15
|
Set target index name:
|
16
16
|
$ rake environment tire:import CLASS='Article' INDEX='articles-new'
|
17
17
|
DESC
|
18
|
-
desc
|
18
|
+
desc full_comment_import
|
19
19
|
task :import do |t|
|
20
20
|
|
21
21
|
def elapsed_to_human(elapsed)
|
@@ -24,18 +24,18 @@ namespace :tire do
|
|
24
24
|
|
25
25
|
case elapsed
|
26
26
|
when 0..59
|
27
|
-
"#{sprintf("%1.
|
27
|
+
"#{sprintf("%1.2f", elapsed)} seconds"
|
28
28
|
when 60..hour-1
|
29
|
-
"#{elapsed/60} minutes and #{elapsed % 60} seconds"
|
29
|
+
"#{(elapsed/60).floor} minutes and #{(elapsed % 60).floor} seconds"
|
30
30
|
when hour..day
|
31
|
-
"#{elapsed/hour} hours and #{elapsed % hour} minutes"
|
31
|
+
"#{(elapsed/hour).floor} hours and #{(elapsed/60 % hour).floor} minutes"
|
32
32
|
else
|
33
|
-
"#{elapsed/hour} hours"
|
33
|
+
"#{(elapsed/hour).round} hours"
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
37
|
if ENV['CLASS'].to_s == ''
|
38
|
-
puts '='*90, 'USAGE', '='*90,
|
38
|
+
puts '='*90, 'USAGE', '='*90, full_comment_import, ""
|
39
39
|
exit(1)
|
40
40
|
end
|
41
41
|
|
@@ -52,16 +52,19 @@ namespace :tire do
|
|
52
52
|
end
|
53
53
|
|
54
54
|
unless index.exists?
|
55
|
-
mapping =
|
56
|
-
MultiJson.encode(klass.tire.mapping_to_hash)
|
55
|
+
mapping = MultiJson.encode(klass.tire.mapping_to_hash, :pretty => Tire::Configuration.pretty)
|
57
56
|
puts "[IMPORT] Creating index '#{index.name}' with mapping:", mapping
|
58
|
-
index.create :mappings => klass.tire.mapping_to_hash, :settings => klass.tire.settings
|
57
|
+
unless index.create( :mappings => klass.tire.mapping_to_hash, :settings => klass.tire.settings )
|
58
|
+
STDERR.puts "[ERROR] There has been an error when creating the index -- elasticsearch returned:",
|
59
|
+
index.response
|
60
|
+
exit(1)
|
61
|
+
end
|
59
62
|
end
|
60
63
|
|
61
64
|
STDOUT.sync = true
|
62
65
|
puts "[IMPORT] Starting import for the '#{ENV['CLASS']}' class"
|
63
66
|
tty_cols = 80
|
64
|
-
total = klass.
|
67
|
+
total = klass.count rescue nil
|
65
68
|
offset = (total.to_s.size*2)+8
|
66
69
|
done = 0
|
67
70
|
|
@@ -101,7 +104,7 @@ namespace :tire do
|
|
101
104
|
|
102
105
|
namespace :index do
|
103
106
|
|
104
|
-
|
107
|
+
full_comment_drop = <<-DESC.gsub(/ /, '')
|
105
108
|
Delete indices passed in the INDEX environment variable; separate multiple indices by comma.
|
106
109
|
|
107
110
|
Pass name of a single index to drop in the INDEX environmnet variable:
|
@@ -111,12 +114,12 @@ namespace :tire do
|
|
111
114
|
$ rake environment tire:index:drop INDICES=articles-2011-01,articles-2011-02
|
112
115
|
|
113
116
|
DESC
|
114
|
-
desc
|
117
|
+
desc full_comment_drop
|
115
118
|
task :drop do
|
116
119
|
index_names = (ENV['INDEX'] || ENV['INDICES']).to_s.split(/,\s*/)
|
117
120
|
|
118
121
|
if index_names.empty?
|
119
|
-
puts '='*90, 'USAGE', '='*90,
|
122
|
+
puts '='*90, 'USAGE', '='*90, full_comment_drop, ""
|
120
123
|
exit(1)
|
121
124
|
end
|
122
125
|
|