stretchy 0.4.2 → 0.4.3
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.
- 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
|