stretchy 0.4.2 → 0.4.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +12 -0
- data/lib/stretchy/boosts/field_value_boost.rb +36 -0
- data/lib/stretchy/boosts.rb +1 -0
- data/lib/stretchy/builders/boost_builder.rb +6 -2
- data/lib/stretchy/builders/where_builder.rb +0 -1
- data/lib/stretchy/clauses/base.rb +38 -70
- data/lib/stretchy/clauses/boost_clause.rb +60 -49
- data/lib/stretchy/clauses/boost_match_clause.rb +26 -36
- data/lib/stretchy/clauses/boost_where_clause.rb +28 -40
- data/lib/stretchy/clauses/match_clause.rb +23 -66
- data/lib/stretchy/clauses/where_clause.rb +20 -32
- data/lib/stretchy/errors/invalid_query_error.rb +6 -0
- data/lib/stretchy/errors.rb +2 -1
- data/lib/stretchy/version.rb +1 -1
- data/stretchy.gemspec +2 -2
- metadata +12 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 90bdaff51a1aaeb776e881571be1b4a0c54da5a3
|
4
|
+
data.tar.gz: 47f7feae0e64d75661da159d2930c2995524240a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 181d7b11c8b58f9d816a3243d3289f02e7e3511bab007d7ea17a2c931ce121c6bc9391d4e23e3d7eb20a16ade4c0a68acde6dfa4d0c55c9a23aeeecfd7c1134c
|
7
|
+
data.tar.gz: 357a659413a79aaa882bb7f6d7b702425eed047c752be9090a0145e3232228030c1f0c04b8fcbaf155186b7aee5199085c403fefea390b6274d368fa1f962c2e
|
data/README.md
CHANGED
@@ -178,6 +178,18 @@ The `:scale` param determines how quickly the value falls off. In the example ab
|
|
178
178
|
|
179
179
|
See the [Function Score Query](http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html) section on Decay Functions for more info.
|
180
180
|
|
181
|
+
### Field
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
query = query.boost.field(:popularity)
|
185
|
+
.boost.field(:timestamp, factor: 0.5, modifier: :sqrt)
|
186
|
+
.boost.field(:votes, :bookmarks, :comments)
|
187
|
+
```
|
188
|
+
|
189
|
+
Boosts a document by a numeric value contained in the specified fields. You can also specify a `factor` (an amount to multiply the field value by) and a `modifier` (a function for normalizing values).
|
190
|
+
|
191
|
+
See the [Boosting By Popularity Guide](https://www.elastic.co/guide/en/elasticsearch/guide/current/boosting-by-popularity.html) and the [Field Value Factor documentation](http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#_field_value_factor) for more info.
|
192
|
+
|
181
193
|
### Random
|
182
194
|
|
183
195
|
```ruby
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Stretchy
|
2
|
+
module Boosts
|
3
|
+
class FieldValueBoost < Base
|
4
|
+
|
5
|
+
MODIFIERS = [:none, :log, :log1p, :log2p, :ln, :ln1p, :ln2p, :square, :sqrt, :reciprocal]
|
6
|
+
|
7
|
+
attribute :field
|
8
|
+
attribute :modifier
|
9
|
+
attribute :factor
|
10
|
+
|
11
|
+
validations do
|
12
|
+
rule :field, :field
|
13
|
+
rule :modifier, inclusion: {in: MODIFIERS}
|
14
|
+
rule :factor, type: {classes: Numeric}
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(field, options = {})
|
18
|
+
@field = field
|
19
|
+
@modifier = options[:modifier]
|
20
|
+
@factor = options[:factor]
|
21
|
+
validate!
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_search
|
25
|
+
json = { field: field }
|
26
|
+
json[:modifier] = modifier if modifier
|
27
|
+
json[:factor] = factor if factor
|
28
|
+
|
29
|
+
{
|
30
|
+
field_value_factor: json
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/stretchy/boosts.rb
CHANGED
@@ -2,6 +2,10 @@ module Stretchy
|
|
2
2
|
module Builders
|
3
3
|
class BoostBuilder
|
4
4
|
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
delegate [:any?, :count, :length] => :functions
|
8
|
+
|
5
9
|
attr_accessor :functions, :overall_boost, :max_boost, :score_mode, :boost_mode
|
6
10
|
|
7
11
|
def initialize
|
@@ -12,8 +16,8 @@ module Stretchy
|
|
12
16
|
@boost_mode = 'sum'
|
13
17
|
end
|
14
18
|
|
15
|
-
def
|
16
|
-
@functions
|
19
|
+
def add_boost(boost)
|
20
|
+
@functions << boost
|
17
21
|
end
|
18
22
|
|
19
23
|
def to_search(query_or_filter)
|
@@ -21,32 +21,32 @@ module Stretchy
|
|
21
21
|
delegate [:request, :response, :results, :ids, :hits, :query,
|
22
22
|
:took, :shards, :total, :max_score, :total_pages] => :query_results
|
23
23
|
delegate [:to_search] => :base
|
24
|
-
delegate [:range, :geo, :terms] => :
|
25
|
-
delegate [:fulltext] => :
|
24
|
+
delegate [:where, :range, :geo, :terms, :not] => :build_where
|
25
|
+
delegate [:match, :fulltext] => :build_match
|
26
26
|
|
27
27
|
#
|
28
28
|
# Generates a chainable query. The only required option for the
|
29
29
|
# first initialization is `:type` , which specifies what type
|
30
30
|
# to query on your index.
|
31
31
|
#
|
32
|
-
# @overload initialize(base_or_opts,
|
32
|
+
# @overload initialize(base_or_opts, params)
|
33
33
|
# @param base [Base] another clause to copy attributes from
|
34
|
-
# @param
|
34
|
+
# @param params [Hash] params to set on the new state
|
35
35
|
#
|
36
36
|
# @overload initialize(base_or_opts)
|
37
37
|
# @option base_or_opts [String] :index The Elastic index to query
|
38
38
|
# @option base_or_opts [String] :type The Lucene type to query on
|
39
39
|
# @option base_or_opts [true, false] :inverse Whether we are in a `not` context
|
40
|
-
def initialize(base = nil,
|
40
|
+
def initialize(base = nil, params = {})
|
41
41
|
if base.is_a?(Builders::ShellBuilder)
|
42
42
|
@base = base
|
43
43
|
elsif base.nil?
|
44
44
|
@base = Builders::ShellBuilder.new
|
45
|
+
@base.index ||= params[:index] || Stretchy.index_name
|
46
|
+
@base.type = params[:type] if params[:type]
|
45
47
|
else
|
46
48
|
@base = Builders::ShellBuilder.new(base)
|
47
49
|
end
|
48
|
-
@base.index ||= options[:index] || Stretchy.index_name
|
49
|
-
@base.type = options[:type] if options[:type]
|
50
50
|
end
|
51
51
|
|
52
52
|
#
|
@@ -101,8 +101,8 @@ module Stretchy
|
|
101
101
|
# @option per_page [Integer] :per_page (DEFAULT_LIMIT) Number of results per page
|
102
102
|
#
|
103
103
|
# @return [self] Allows continuing the query chain
|
104
|
-
def page(num,
|
105
|
-
base.limit =
|
104
|
+
def page(num, params = {})
|
105
|
+
base.limit = params[:limit] || params[:per_page] || get_limit
|
106
106
|
base.offset = [(num - 1), 0].max.ceil * get_limit
|
107
107
|
self
|
108
108
|
end
|
@@ -160,36 +160,13 @@ module Stretchy
|
|
160
160
|
!!base.explain
|
161
161
|
end
|
162
162
|
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
#
|
170
|
-
# @return [MatchClause] query state with fulltext matches
|
171
|
-
#
|
172
|
-
# @see MatchClause#initialize
|
173
|
-
#
|
174
|
-
# @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html Elastic Docs - Match Query
|
175
|
-
def match(options = {})
|
176
|
-
MatchClause.new(base, options)
|
177
|
-
end
|
178
|
-
|
179
|
-
#
|
180
|
-
# Used for filtering results. Works similarly to
|
181
|
-
# ActiveRecord's `where` method.
|
182
|
-
#
|
183
|
-
# @param options = {} [Hash] Options to be passed to
|
184
|
-
# the {WhereClause}
|
185
|
-
#
|
186
|
-
# @return [WhereClause] query state with filters
|
187
|
-
#
|
188
|
-
# @see WhereClause#initialize
|
189
|
-
def where(options = {})
|
190
|
-
WhereClause.new(base, options)
|
163
|
+
def not(params = {}, options = {})
|
164
|
+
if params.is_a?(String)
|
165
|
+
build_match.not(params, options)
|
166
|
+
else
|
167
|
+
build_where.not(params, options)
|
168
|
+
end
|
191
169
|
end
|
192
|
-
alias :filter :where
|
193
170
|
|
194
171
|
#
|
195
172
|
# Used for boosting the relevance score of
|
@@ -210,40 +187,17 @@ module Stretchy
|
|
210
187
|
BoostClause.new(base)
|
211
188
|
end
|
212
189
|
|
213
|
-
#
|
214
|
-
# Inverts the current context - the next method
|
215
|
-
# called, such as {#where} or {#match} will generate
|
216
|
-
# a filter specifying the document **does not**
|
217
|
-
# match the specified filter.
|
218
|
-
#
|
219
|
-
# @overload not(string)
|
220
|
-
# @param [String] A string that must not be anywhere
|
221
|
-
# in the document
|
222
|
-
#
|
223
|
-
# @overload not(opts_or_string)
|
224
|
-
# @param [Hash] Options to be passed to an inverted {WhereClause}
|
225
|
-
#
|
226
|
-
# @return [Base] A {WhereClause}, or a {MatchClause} if only a string
|
227
|
-
# is given (ie, doing a full-text search across the whole document)
|
228
|
-
def not(opts_or_string = {})
|
229
|
-
if opts_or_string.is_a?(Hash)
|
230
|
-
WhereClause.new(base).not(opts_or_string)
|
231
|
-
else
|
232
|
-
MatchClause.new(base).not(opts_or_string)
|
233
|
-
end
|
234
|
-
end
|
235
|
-
|
236
190
|
#
|
237
191
|
# Adds filters in the `should` context. Operates just like
|
238
192
|
# {#where}, but these filters only serve to add to the
|
239
193
|
# relevance score of the returned documents, rather than
|
240
194
|
# being required to match.
|
241
195
|
#
|
242
|
-
# @overload should(
|
196
|
+
# @overload should(params)
|
243
197
|
# @param [String] A string to match via full-text search
|
244
198
|
# anywhere in the document.
|
245
199
|
#
|
246
|
-
# @overload should(
|
200
|
+
# @overload should(params)
|
247
201
|
# @param [Hash] Options to generate filters.
|
248
202
|
#
|
249
203
|
# @return [WhereClause] current query state with should clauses applied
|
@@ -251,22 +205,22 @@ module Stretchy
|
|
251
205
|
# @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html Elastic Docs - Bool Query
|
252
206
|
#
|
253
207
|
# @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-filter.html Elastic Docs - Bool Filter
|
254
|
-
def should(
|
255
|
-
if
|
256
|
-
WhereClause.new(base).should(
|
208
|
+
def should(params = {}, options = {})
|
209
|
+
if params.is_a?(Hash)
|
210
|
+
WhereClause.new(base).should(params, options)
|
257
211
|
else
|
258
|
-
MatchClause.new(base).should(
|
212
|
+
MatchClause.new(base).should(params, options)
|
259
213
|
end
|
260
214
|
end
|
261
215
|
|
262
216
|
#
|
263
217
|
# Allows adding raw aggregation JSON to your
|
264
218
|
# query
|
265
|
-
# @param
|
219
|
+
# @param params = {} [Hash] JSON to aggregate on
|
266
220
|
#
|
267
221
|
# @return [self] Allows continuing the query chain
|
268
|
-
def aggregations(
|
269
|
-
base.aggregate_builder = base.aggregate_builder.merge(
|
222
|
+
def aggregations(params = {})
|
223
|
+
base.aggregate_builder = base.aggregate_builder.merge(params)
|
270
224
|
self
|
271
225
|
end
|
272
226
|
alias :aggs :aggregations
|
@@ -294,6 +248,20 @@ module Stretchy
|
|
294
248
|
@query_results ||= Stretchy::Results::Base.new(base)
|
295
249
|
end
|
296
250
|
|
251
|
+
protected
|
252
|
+
|
253
|
+
def build_match
|
254
|
+
MatchClause.new(base)
|
255
|
+
end
|
256
|
+
|
257
|
+
def build_where
|
258
|
+
WhereClause.new(base)
|
259
|
+
end
|
260
|
+
|
261
|
+
def hashify_params(params)
|
262
|
+
params.is_a?(String) ? { '_all' => params } : params
|
263
|
+
end
|
264
|
+
|
297
265
|
end
|
298
266
|
end
|
299
267
|
end
|
@@ -22,32 +22,18 @@ module Stretchy
|
|
22
22
|
|
23
23
|
delegate [:geo, :range] => :where
|
24
24
|
|
25
|
-
#
|
26
|
-
# Switch to the boost state, specifying that
|
27
|
-
# the next where / range / etc will be a boost
|
28
|
-
# instead of a regular filter / range / etc.
|
29
|
-
#
|
30
|
-
# @param base [Base] a clause to copy query state from
|
31
|
-
# @param options = {} [Hash] Options for the boost clause
|
32
|
-
# @option options [true, false] :inverse (nil) If this boost should also be in the inverse state
|
33
|
-
#
|
34
|
-
def initialize(base)
|
35
|
-
super(base)
|
36
|
-
end
|
37
|
-
|
38
25
|
#
|
39
26
|
# Changes query state to "match" in the context
|
40
27
|
# of boosting. Options here work the same way as
|
41
28
|
# {MatchClause#initialize}, but the combined query
|
42
29
|
# will be applied as a boost function.
|
43
30
|
#
|
44
|
-
# @param
|
31
|
+
# @param params = {} [Hash] params for full text matching
|
45
32
|
#
|
46
33
|
# @return [BoostMatchClause] query with boost match state
|
47
|
-
def match(options = {})
|
48
|
-
BoostMatchClause.new(base, options)
|
34
|
+
def match(params = {}, options = {})
|
35
|
+
BoostMatchClause.new(base).boost_match(params, options)
|
49
36
|
end
|
50
|
-
alias :fulltext :match
|
51
37
|
|
52
38
|
#
|
53
39
|
# Changes query state to "where" in the context
|
@@ -55,15 +41,51 @@ module Stretchy
|
|
55
41
|
# but applies the generated filters as a boost
|
56
42
|
# function.
|
57
43
|
#
|
58
|
-
# @param
|
44
|
+
# @param params = {} [Hash] Filters to use in this boost.
|
59
45
|
#
|
60
46
|
# @return [BoostWhereClause] Query state with boost filters applied
|
61
47
|
#
|
62
|
-
def where(options = {})
|
63
|
-
BoostWhereClause.new(base, options)
|
48
|
+
def where(params = {}, options = {})
|
49
|
+
BoostWhereClause.new(base).boost_where(params, options)
|
64
50
|
end
|
65
51
|
alias :filter :where
|
66
52
|
|
53
|
+
|
54
|
+
#
|
55
|
+
# Adds a boost based on the value in the specified field.
|
56
|
+
# You can pass more than one field as arguments, and
|
57
|
+
# you can also pass the `factor` and `modifier` options
|
58
|
+
# as an options hash.
|
59
|
+
#
|
60
|
+
# **CAUTION:** All documents in the index _must_ have
|
61
|
+
# a numeric value for any fields specified here, or
|
62
|
+
# the query will fail.
|
63
|
+
#
|
64
|
+
# @example Adding two fields with options
|
65
|
+
# query = query.boost.field(:numeric_field, :other_field, factor: 7, modifier: :log2p)
|
66
|
+
#
|
67
|
+
# @param *args [Arguments] Fields to add to the document score
|
68
|
+
# @param options = {} [Hash] Options to pass to the field_value_factor boost
|
69
|
+
#
|
70
|
+
# @return [self] Query state with field boosts applied
|
71
|
+
#
|
72
|
+
# @see https://www.elastic.co/guide/en/elasticsearch/guide/current/boosting-by-popularity.html Elasticsearch guide on boosting by popularity
|
73
|
+
#
|
74
|
+
# @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#_field_value_factor Elasticsearch field value factor reference
|
75
|
+
#
|
76
|
+
def field(*args)
|
77
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
78
|
+
args.each do |field|
|
79
|
+
pp
|
80
|
+
base.boost_builder.add_boost(Boosts::FieldValueBoost.new(field, options))
|
81
|
+
end
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
def not(*args)
|
86
|
+
raise Errors::InvalidQueryError.new("Cannot call .not directly after boost - use .where.not or .match.not instead")
|
87
|
+
end
|
88
|
+
|
67
89
|
#
|
68
90
|
# Adds a {Boosts::FieldDecayBoost}, which boosts
|
69
91
|
# a search result based on how close it is to a
|
@@ -76,18 +98,18 @@ module Stretchy
|
|
76
98
|
# * `:origin` or `:lat` & `:lng` combo
|
77
99
|
# * `:scale`
|
78
100
|
#
|
79
|
-
# @option
|
80
|
-
# @option
|
81
|
-
# @option
|
82
|
-
# @option
|
83
|
-
# @option
|
84
|
-
# @option
|
85
|
-
# @option
|
86
|
-
# @option
|
87
|
-
# @option
|
88
|
-
# @option
|
89
|
-
# @option
|
90
|
-
# @option
|
101
|
+
# @option params [Numeric] :field What field to check with this boost
|
102
|
+
# @option params [Date, Time, Numeric, Types::GeoPoint] :origin Boost score based on how close the field is to this value. Required unless {Types::GeoPoint} is present (:lat, :lng, etc)
|
103
|
+
# @option params [Numeric] :lat Latitude, for a geo point
|
104
|
+
# @option params [Numeric] :latitude Latitude, for a geo point
|
105
|
+
# @option params [Numeric] :lng Longitude, for a geo point
|
106
|
+
# @option params [Numeric] :lon Longitude, for a geo point
|
107
|
+
# @option params [Numeric] :longitude Longitude, for a geo point
|
108
|
+
# @option params [String] :scale When the field is this distance from origin, the boost will be multiplied by `:decay` . Default is 0.5, so when `:origin` is a geo point and `:scale` is '10mi', then this boost will be twice as much for a point at the origin as for one 10 miles away
|
109
|
+
# @option params [String] :offset Anything within this distance of the origin is boosted as if it were at the origin
|
110
|
+
# @option params [Symbol] :type (:gauss) What type of decay to use. One of `:linear`, `:exp`, or `:gauss`
|
111
|
+
# @option params [Numeric] :decay_amount (0.5) How much the boost falls off when it is `:scale` distance from `:origin`
|
112
|
+
# @option params [Numeric] :weight (1.2) How strongly to weight this boost compared to others
|
91
113
|
#
|
92
114
|
# @example Boost near a geo point
|
93
115
|
# query.boost.near(
|
@@ -105,7 +127,7 @@ module Stretchy
|
|
105
127
|
# scale: '3d'
|
106
128
|
# )
|
107
129
|
#
|
108
|
-
# @example Boost near a number (with
|
130
|
+
# @example Boost near a number (with params)
|
109
131
|
# query.boost.near(
|
110
132
|
# field: :followers,
|
111
133
|
# origin: 100,
|
@@ -119,13 +141,13 @@ module Stretchy
|
|
119
141
|
# @return [Base] Query with field decay filter added
|
120
142
|
#
|
121
143
|
# @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html Elastic Docs - Function Score Query
|
122
|
-
def near(options = {})
|
123
|
-
if
|
124
|
-
|
144
|
+
def near(params = {}, options = {})
|
145
|
+
if params[:lat] || params[:latitude] ||
|
146
|
+
params[:lng] || params[:longitude] || params[:lon]
|
125
147
|
|
126
|
-
|
148
|
+
params[:origin] = Stretchy::Types::GeoPoint.new(params)
|
127
149
|
end
|
128
|
-
base.boost_builder.
|
150
|
+
base.boost_builder.add_boost Stretchy::Boosts::FieldDecayBoost.new(params)
|
129
151
|
Base.new(base)
|
130
152
|
end
|
131
153
|
alias :geo :near
|
@@ -191,17 +213,6 @@ module Stretchy
|
|
191
213
|
self
|
192
214
|
end
|
193
215
|
|
194
|
-
#
|
195
|
-
# Switches to inverse context - boosts added with {#where}
|
196
|
-
# and #{match} will be applied to documents which *do not*
|
197
|
-
# match said filters.
|
198
|
-
#
|
199
|
-
# @return [BoostClause] Boost clause in inverse context
|
200
|
-
def not(options = {})
|
201
|
-
@inverse = true
|
202
|
-
self
|
203
|
-
end
|
204
|
-
|
205
216
|
end
|
206
217
|
end
|
207
218
|
end
|
@@ -15,46 +15,36 @@ module Stretchy
|
|
15
15
|
|
16
16
|
delegate [:range, :geo] => :where
|
17
17
|
|
18
|
-
#
|
19
|
-
# Adds a match query to the boost functions.
|
20
|
-
#
|
21
|
-
# @overload initialize(base, opts_or_string)
|
22
|
-
# @param base [Base] Base query to copy data from
|
23
|
-
# @param opts_or_string [String] String to do a free-text match across the document
|
24
|
-
#
|
25
|
-
# @overload initialize(base, opts_or_string)
|
26
|
-
# @param base [Base] Base query to copy data from
|
27
|
-
# @param options = {} [Hash] Fields and values to match via full-text search
|
28
|
-
#
|
29
|
-
# @return [BoostMatchClause] Boost clause in match context, with queries applied
|
30
|
-
def initialize(base, opts_or_string = {}, options = {})
|
31
|
-
super(base)
|
32
|
-
if opts_or_string.is_a?(Hash)
|
33
|
-
match_function(opts_or_string.merge(options))
|
34
|
-
else
|
35
|
-
match_function(options.merge('_all' => opts_or_string))
|
36
|
-
end
|
37
|
-
end
|
38
|
-
|
39
18
|
#
|
40
19
|
# Switches to inverse context, and applies filters as inverse
|
41
20
|
# options (ie, documents that *do not* match the query will
|
42
21
|
# be boosted)
|
43
22
|
#
|
44
|
-
# @overload not(
|
23
|
+
# @overload not(params)
|
45
24
|
# @param [String] String that must not match anywhere in the document
|
46
25
|
#
|
47
|
-
# @overload not(
|
48
|
-
# @param
|
26
|
+
# @overload not(params)
|
27
|
+
# @param params [Hash] Fields and values that should not match in the document
|
49
28
|
#
|
50
29
|
# @return [BoostMatchClause] Query with inverse matching boost function applied
|
51
|
-
def not(
|
30
|
+
def not(params = {})
|
52
31
|
@inverse = true
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
32
|
+
match_function(hashify_params(params))
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def boost_match(params = {}, options = {})
|
37
|
+
match_function(hashify_params(params), options)
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
def fulltext(params = {}, options = {})
|
42
|
+
weight = params.delete(:weight) || options[:weight]
|
43
|
+
options[:min] = 1
|
44
|
+
options[:slop] = MatchClause::FULLTEXT_SLOP
|
45
|
+
clause = MatchClause.new.match(params, options)
|
46
|
+
boost = clause.to_boost(weight)
|
47
|
+
base.boost_builder.add_boost(boost) if boost
|
58
48
|
self
|
59
49
|
end
|
60
50
|
|
@@ -72,7 +62,7 @@ module Stretchy
|
|
72
62
|
#
|
73
63
|
# @return [WhereClause] Query with where clause applied
|
74
64
|
def where(*args)
|
75
|
-
WhereClause.new(base
|
65
|
+
WhereClause.new(base).where(*args)
|
76
66
|
end
|
77
67
|
|
78
68
|
#
|
@@ -89,16 +79,16 @@ module Stretchy
|
|
89
79
|
#
|
90
80
|
# @return [MatchClause] Base context with match queries applied
|
91
81
|
def match(*args)
|
92
|
-
MatchClause.new(base
|
82
|
+
MatchClause.new(base).match(*args)
|
93
83
|
end
|
94
84
|
|
95
85
|
private
|
96
86
|
|
97
|
-
def match_function(options = {})
|
98
|
-
weight =
|
99
|
-
clause = MatchClause.
|
87
|
+
def match_function(params = {}, options = {})
|
88
|
+
weight = params.delete(:weight) || options[:weight]
|
89
|
+
clause = MatchClause.new.match(params, options)
|
100
90
|
boost = clause.to_boost(weight)
|
101
|
-
base.boost_builder.
|
91
|
+
base.boost_builder.add_boost(boost) if boost
|
102
92
|
end
|
103
93
|
|
104
94
|
end
|
@@ -11,18 +11,12 @@ module Stretchy
|
|
11
11
|
#
|
12
12
|
class BoostWhereClause < BoostClause
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
# @see {WhereClause#initialize}
|
21
|
-
#
|
22
|
-
# @return [BoostWhereClause] Query with filter boosts applied
|
23
|
-
def initialize(base, options = {})
|
24
|
-
super(base)
|
25
|
-
where_function(:init, options) if options.any?
|
14
|
+
def boost_where(params = {}, options = {})
|
15
|
+
weight = params.delete(:weight) || options[:weight]
|
16
|
+
options[:inverse] = true if inverse?
|
17
|
+
clause = WhereClause.new.where(params, options)
|
18
|
+
boost = clause.to_boost(weight)
|
19
|
+
base.boost_builder.add_boost(boost) if boost
|
26
20
|
self
|
27
21
|
end
|
28
22
|
|
@@ -40,7 +34,12 @@ module Stretchy
|
|
40
34
|
#
|
41
35
|
# @return [WhereClause] Query with where clause applied
|
42
36
|
def where(*args)
|
43
|
-
WhereClause.new(base
|
37
|
+
WhereClause.new(base).where(*args)
|
38
|
+
end
|
39
|
+
|
40
|
+
def not(params = {}, options = {})
|
41
|
+
@inverse = true
|
42
|
+
boost_where(params, options)
|
44
43
|
end
|
45
44
|
|
46
45
|
#
|
@@ -57,7 +56,7 @@ module Stretchy
|
|
57
56
|
#
|
58
57
|
# @return [MatchClause] Base context with match queries applied
|
59
58
|
def match(*args)
|
60
|
-
MatchClause.new(base
|
59
|
+
MatchClause.new(base).match(*args)
|
61
60
|
end
|
62
61
|
|
63
62
|
#
|
@@ -71,8 +70,14 @@ module Stretchy
|
|
71
70
|
# @see http://www.elastic.co/guide/en/elasticsearch/guide/master/_ranges.html Elastic Guides - Ranges
|
72
71
|
#
|
73
72
|
# @return [Base] Query in base context with range boost applied
|
74
|
-
def range(
|
75
|
-
|
73
|
+
def range(field, options = {})
|
74
|
+
weight = options[:weight]
|
75
|
+
options[:inverse] = true if inverse?
|
76
|
+
|
77
|
+
clause = WhereClause.new.range(field, options)
|
78
|
+
boost = clause.to_boost(weight)
|
79
|
+
base.boost_builder.add_boost(boost) if boost
|
80
|
+
|
76
81
|
Base.new(base)
|
77
82
|
end
|
78
83
|
|
@@ -91,32 +96,15 @@ module Stretchy
|
|
91
96
|
# @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-geo-distance-filter.html Elastic Docs - Geo Distance Filter
|
92
97
|
#
|
93
98
|
# @return [Base] Query in base context with geo filter boost applied
|
94
|
-
def geo(
|
95
|
-
|
99
|
+
def geo(field, options = {})
|
100
|
+
weight = options[:weight]
|
101
|
+
options[:inverse] = true if inverse?
|
102
|
+
|
103
|
+
clause = WhereClause.new.geo(field, options)
|
104
|
+
boost = clause.to_boost(weight)
|
105
|
+
base.boost_builder.add_boost(boost) if boost
|
96
106
|
Base.new(base)
|
97
107
|
end
|
98
|
-
|
99
|
-
private
|
100
|
-
|
101
|
-
def add_params(params = {})
|
102
|
-
where_function(:init, params)
|
103
|
-
end
|
104
|
-
|
105
|
-
def where_function(method, *args)
|
106
|
-
options = args.last.is_a?(Hash) ? args.pop : {}
|
107
|
-
weight = options.delete(:weight)
|
108
|
-
|
109
|
-
clause = nil
|
110
|
-
if method == :init
|
111
|
-
clause = WhereClause.tmp(options.merge(inverse: inverse?))
|
112
|
-
else
|
113
|
-
args.push(options)
|
114
|
-
clause = WhereClause.tmp(inverse: inverse?).send(method, *args)
|
115
|
-
end
|
116
|
-
boost = clause.to_boost(weight)
|
117
|
-
|
118
|
-
base.boost_builder.functions << boost if boost
|
119
|
-
end
|
120
108
|
end
|
121
109
|
end
|
122
110
|
end
|
@@ -11,56 +11,13 @@ module Stretchy
|
|
11
11
|
#
|
12
12
|
class MatchClause < Base
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
# query scope by using a new {Base}. Primarily
|
17
|
-
# used in {BoostClause} for boosting on full-text
|
18
|
-
# matches.
|
19
|
-
#
|
20
|
-
# @param options = {} [Hash] Options to pass to the full-text match
|
21
|
-
#
|
22
|
-
# @return [MatchClause] Temporary clause outside current state
|
23
|
-
def self.tmp(options = {})
|
24
|
-
if options.delete(:inverse)
|
25
|
-
self.new(Builders::ShellBuilder.new).not(options)
|
26
|
-
else
|
27
|
-
self.new(Builders::ShellBuilder.new, options)
|
28
|
-
end
|
29
|
-
end
|
14
|
+
FULLTEXT_SLOP = 50
|
15
|
+
FULLTEXT_MIN = 1
|
30
16
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
# @param [Base] Base clause to copy data from
|
36
|
-
# @param [String] Performs a full-text query for this string on all fields in the document.
|
37
|
-
#
|
38
|
-
# @overload initialize(base, opts_or_string)
|
39
|
-
# @param [Base] Base clause to copy data from
|
40
|
-
# @param [Hash] A hash of fields and values to perform full-text matches with
|
41
|
-
# @option opts_or_string [String] :operator ('and') Allows switching from the default 'and'
|
42
|
-
# operator to 'or', for all the specified fields
|
43
|
-
#
|
44
|
-
# @example A basic full-text match
|
45
|
-
# query.match("anywhere in document")
|
46
|
-
#
|
47
|
-
# @example A full-text search on specific fields
|
48
|
-
# query.match(
|
49
|
-
# my_field: "match in my_field",
|
50
|
-
# other_field: "match in other_field"
|
51
|
-
# )
|
52
|
-
#
|
53
|
-
# @example A full-text search using the 'or' operator
|
54
|
-
# query.match(
|
55
|
-
# my_field: 'match any of these',
|
56
|
-
# other_field: 'other words',
|
57
|
-
# operator: 'or'
|
58
|
-
# )
|
59
|
-
#
|
60
|
-
# @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-match-query.html Elastic Docs - Match Query
|
61
|
-
def initialize(base, opts_or_str = {})
|
62
|
-
super(base)
|
63
|
-
add_params(opts_or_str)
|
17
|
+
def match(params = {}, options = {})
|
18
|
+
@inverse = false unless should?
|
19
|
+
add_params(hashify_params(params), options)
|
20
|
+
self
|
64
21
|
end
|
65
22
|
|
66
23
|
#
|
@@ -79,12 +36,12 @@ module Stretchy
|
|
79
36
|
# * "the quick dog jumped over"
|
80
37
|
# * "the adoreable puppy jumped over" **not returned**
|
81
38
|
#
|
82
|
-
# @overload phrase(
|
83
|
-
# @param
|
39
|
+
# @overload phrase(params)
|
40
|
+
# @param params = {} [String] A phrase that will
|
84
41
|
# be matched anywhere in the document
|
85
42
|
#
|
86
|
-
# @overload phrase(
|
87
|
-
# @param
|
43
|
+
# @overload phrase(params)
|
44
|
+
# @param params = {} [Hash] A hash of fields and phrases
|
88
45
|
# that should be matched in those fields
|
89
46
|
#
|
90
47
|
# @example Matching multiple words together
|
@@ -96,9 +53,9 @@ module Stretchy
|
|
96
53
|
# @return [self] Allows continuing the query chain
|
97
54
|
#
|
98
55
|
# @see https://www.elastic.co/guide/en/elasticsearch/guide/current/proximity-relevance.html Elasticsearch guide: proximity for relevance
|
99
|
-
def fulltext(
|
100
|
-
add_params(
|
101
|
-
add_params(
|
56
|
+
def fulltext(params = {})
|
57
|
+
add_params(params, min: FULLTEXT_MIN)
|
58
|
+
add_params(params, should: true, slop: FULLTEXT_SLOP)
|
102
59
|
self
|
103
60
|
end
|
104
61
|
|
@@ -106,9 +63,9 @@ module Stretchy
|
|
106
63
|
# Switches to inverted context. Matches applied here work the same way as
|
107
64
|
# {#initialize}, but returned documents must **not** match these filters.
|
108
65
|
#
|
109
|
-
# @overload not(
|
66
|
+
# @overload not(params)
|
110
67
|
# @param [String] A string that must not be matched anywhere in the document
|
111
|
-
# @overload not(
|
68
|
+
# @overload not(params)
|
112
69
|
# @param [Hash] A hash of fields and strings that must not be matched in those fields
|
113
70
|
#
|
114
71
|
# @return [MatchClause] inverted query state with match filters applied
|
@@ -121,9 +78,9 @@ module Stretchy
|
|
121
78
|
# my_field: "not_match_1",
|
122
79
|
# other_field: "not_match_2"
|
123
80
|
# )
|
124
|
-
def not(
|
81
|
+
def not(params = {}, options = {})
|
125
82
|
@inverse = true
|
126
|
-
add_params(
|
83
|
+
add_params(params, options)
|
127
84
|
self
|
128
85
|
end
|
129
86
|
|
@@ -134,9 +91,9 @@ module Stretchy
|
|
134
91
|
#
|
135
92
|
# Can be chained with {#not}
|
136
93
|
#
|
137
|
-
# @overload should(
|
94
|
+
# @overload should(params)
|
138
95
|
# @param [String] A string that should be matched anywhere in the document
|
139
|
-
# @overload should(
|
96
|
+
# @overload should(params)
|
140
97
|
# @param [Hash] A hash of fields and strings that should be matched in those fields
|
141
98
|
#
|
142
99
|
# @return [MatchClause] query state with should filters added
|
@@ -157,10 +114,10 @@ module Stretchy
|
|
157
114
|
# )
|
158
115
|
#
|
159
116
|
# @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html Elastic Docs - Bool Query
|
160
|
-
def should(
|
117
|
+
def should(params = {}, options = {})
|
161
118
|
@should = true
|
162
119
|
@inverse = false
|
163
|
-
add_params(
|
120
|
+
add_params(params, options)
|
164
121
|
self
|
165
122
|
end
|
166
123
|
|
@@ -204,8 +161,8 @@ module Stretchy
|
|
204
161
|
end
|
205
162
|
|
206
163
|
def add_param(field, param, options = {})
|
207
|
-
options[:inverse] = true if inverse?
|
208
|
-
options[:should] = true if should?
|
164
|
+
options[:inverse] = true if inverse? || options[:inverse]
|
165
|
+
options[:should] = true if should? || options[:should]
|
209
166
|
base.match_builder.add_matches(field, param, options)
|
210
167
|
end
|
211
168
|
|
@@ -23,30 +23,15 @@ module Stretchy
|
|
23
23
|
#
|
24
24
|
class WhereClause < Base
|
25
25
|
|
26
|
-
#
|
27
|
-
# Creates a temporary context by initializing a new Base object.
|
28
|
-
# Used primarily in {BoostWhereClause}
|
29
|
-
#
|
30
|
-
# @param options = {} [Hash] Options to filter on
|
31
|
-
#
|
32
|
-
# @return [WhereClause] A clause outside the main query context
|
33
|
-
def self.tmp(options = {})
|
34
|
-
if options.delete(:inverse)
|
35
|
-
self.new(Builders::ShellBuilder.new).not(options)
|
36
|
-
else
|
37
|
-
self.new(Builders::ShellBuilder.new, options)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
|
41
26
|
#
|
42
27
|
# Options passed to the initializer will be interpreted as filters
|
43
28
|
# to be added to the query. This is similar to ActiveRecord's `where`
|
44
29
|
# method.
|
45
30
|
#
|
46
31
|
# @param base [Base] Used to intialize the new state from the previous clause
|
47
|
-
# @param
|
48
|
-
# @option
|
49
|
-
# @option
|
32
|
+
# @param params = {} [Hash] filters to be applied to the new state
|
33
|
+
# @option params [true, false] :inverted (nil) Whether the new state is inverted
|
34
|
+
# @option params [true, false] :should (nil) Whether the new state is should
|
50
35
|
#
|
51
36
|
# @example Apply ActiveRecord-like filters
|
52
37
|
# query.where(
|
@@ -62,9 +47,11 @@ module Stretchy
|
|
62
47
|
#
|
63
48
|
# @see http://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-filter.html Elastic Docs - Range Filter
|
64
49
|
#
|
65
|
-
def
|
66
|
-
|
67
|
-
|
50
|
+
def where(params = {}, options = {})
|
51
|
+
@inverse = false
|
52
|
+
@should = false
|
53
|
+
add_params(params, options)
|
54
|
+
self
|
68
55
|
end
|
69
56
|
|
70
57
|
#
|
@@ -172,8 +159,9 @@ module Stretchy
|
|
172
159
|
# )
|
173
160
|
#
|
174
161
|
# @see https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-terms-filter.html Elasticsearch Terms Filter
|
175
|
-
def terms(params = {})
|
176
|
-
|
162
|
+
def terms(params = {}, options = {})
|
163
|
+
options[:exact] = true
|
164
|
+
add_params(params, options)
|
177
165
|
self
|
178
166
|
end
|
179
167
|
alias :exact :terms
|
@@ -185,7 +173,7 @@ module Stretchy
|
|
185
173
|
#
|
186
174
|
# Can be chained with {#should} to produce inverted should queries
|
187
175
|
#
|
188
|
-
# @param
|
176
|
+
# @param params = {} [Hash] params to filter on
|
189
177
|
#
|
190
178
|
# @return [WhereClause] inverted query state with not filters applied.
|
191
179
|
#
|
@@ -199,11 +187,11 @@ module Stretchy
|
|
199
187
|
#
|
200
188
|
# @example Inverted should filters
|
201
189
|
# query.should.not(
|
202
|
-
# match_field: [:these, "
|
190
|
+
# match_field: [:these, "params"]
|
203
191
|
# )
|
204
|
-
def not(options = {})
|
192
|
+
def not(params = {}, options = {})
|
205
193
|
@inverse = true
|
206
|
-
add_params(options)
|
194
|
+
add_params(params, options)
|
207
195
|
self
|
208
196
|
end
|
209
197
|
|
@@ -218,25 +206,25 @@ module Stretchy
|
|
218
206
|
# **CAUTION:** Documents that don't match at least one `should`
|
219
207
|
# clause will not be returned.
|
220
208
|
#
|
221
|
-
# @param
|
209
|
+
# @param params = {} [Hash] params to filter on
|
222
210
|
#
|
223
211
|
# @return [WhereClause] should query state with should filters applied
|
224
212
|
#
|
225
|
-
# @example Specifying should
|
213
|
+
# @example Specifying should params
|
226
214
|
# query.should(
|
227
215
|
# field: [99, 27]
|
228
216
|
# )
|
229
217
|
#
|
230
|
-
# @example Inverted should
|
218
|
+
# @example Inverted should params
|
231
219
|
# query.should.not(
|
232
220
|
# exists_field: nil
|
233
221
|
# )
|
234
222
|
#
|
235
223
|
# @see https://www.elastic.co/guide/en/elasticsearch/reference/1.4/query-dsl-bool-query.html Elasticsearch Bool Query docs (bool filter just references this)
|
236
|
-
def should(options = {})
|
224
|
+
def should(params = {}, options = {})
|
237
225
|
@inverse = false
|
238
226
|
@should = true
|
239
|
-
add_params(options)
|
227
|
+
add_params(params, options)
|
240
228
|
self
|
241
229
|
end
|
242
230
|
|
data/lib/stretchy/errors.rb
CHANGED
@@ -1 +1,2 @@
|
|
1
|
-
require 'stretchy/errors/validation_error.rb'
|
1
|
+
require 'stretchy/errors/validation_error.rb'
|
2
|
+
require 'stretchy/errors/invalid_query_error.rb'
|
data/lib/stretchy/version.rb
CHANGED
data/stretchy.gemspec
CHANGED
@@ -21,8 +21,8 @@ Gem::Specification.new do |spec|
|
|
21
21
|
|
22
22
|
spec.add_dependency "elasticsearch", "~> 1.0"
|
23
23
|
spec.add_dependency "excon", "~> 0.45"
|
24
|
-
spec.add_dependency "valid"
|
25
|
-
spec.add_dependency "virtus"
|
24
|
+
spec.add_dependency "valid", "~> 0.5"
|
25
|
+
spec.add_dependency "virtus", "~> 1.0"
|
26
26
|
|
27
27
|
spec.add_development_dependency "bundler", "~> 1.8"
|
28
28
|
spec.add_development_dependency "rake", "~> 10.0"
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stretchy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.
|
4
|
+
version: 0.4.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- agius
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-06-
|
11
|
+
date: 2015-06-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: elasticsearch
|
@@ -42,30 +42,30 @@ dependencies:
|
|
42
42
|
name: valid
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
|
-
- - "
|
45
|
+
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version: '0'
|
47
|
+
version: '0.5'
|
48
48
|
type: :runtime
|
49
49
|
prerelease: false
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
51
51
|
requirements:
|
52
|
-
- - "
|
52
|
+
- - "~>"
|
53
53
|
- !ruby/object:Gem::Version
|
54
|
-
version: '0'
|
54
|
+
version: '0.5'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
56
|
name: virtus
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - "~>"
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '0'
|
61
|
+
version: '1.0'
|
62
62
|
type: :runtime
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
|
-
- - "
|
66
|
+
- - "~>"
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '0'
|
68
|
+
version: '1.0'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: bundler
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -184,6 +184,7 @@ files:
|
|
184
184
|
- lib/stretchy/boosts.rb
|
185
185
|
- lib/stretchy/boosts/base.rb
|
186
186
|
- lib/stretchy/boosts/field_decay_boost.rb
|
187
|
+
- lib/stretchy/boosts/field_value_boost.rb
|
187
188
|
- lib/stretchy/boosts/filter_boost.rb
|
188
189
|
- lib/stretchy/boosts/random_boost.rb
|
189
190
|
- lib/stretchy/builders.rb
|
@@ -201,6 +202,7 @@ files:
|
|
201
202
|
- lib/stretchy/clauses/match_clause.rb
|
202
203
|
- lib/stretchy/clauses/where_clause.rb
|
203
204
|
- lib/stretchy/errors.rb
|
205
|
+
- lib/stretchy/errors/invalid_query_error.rb
|
204
206
|
- lib/stretchy/errors/validation_error.rb
|
205
207
|
- lib/stretchy/filters.rb
|
206
208
|
- lib/stretchy/filters/and_filter.rb
|