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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4ef5c2486425a691f1be06b65dd0fde00c1d17da
4
- data.tar.gz: 2bdbed13f48c91a300da4e246ac95bbb21515d81
3
+ metadata.gz: 90bdaff51a1aaeb776e881571be1b4a0c54da5a3
4
+ data.tar.gz: 47f7feae0e64d75661da159d2930c2995524240a
5
5
  SHA512:
6
- metadata.gz: 6774e7ae30ac2517dbcb588f8791a6af765f23c5f9923259470634254640a785a8456b5a565cb5af00c077113127dc6a7133f2fd54587c6d7bacb6d168df5e5e
7
- data.tar.gz: 441b5aa1b2bfb77f41d42105e9744e0815fd7f95ed2887e5d56f4c6bd133ee1d2220427a46e77bc0e345dd16e9f81d9c9201d8551b1d4d11ffa27d1cfafe9b70
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
@@ -1,4 +1,5 @@
1
1
  require 'stretchy/boosts/base'
2
2
  require 'stretchy/boosts/field_decay_boost'
3
+ require 'stretchy/boosts/field_value_boost'
3
4
  require 'stretchy/boosts/filter_boost'
4
5
  require 'stretchy/boosts/random_boost'
@@ -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 any?
16
- @functions.any?
19
+ def add_boost(boost)
20
+ @functions << boost
17
21
  end
18
22
 
19
23
  def to_search(query_or_filter)
@@ -38,7 +38,6 @@ module Stretchy
38
38
  else
39
39
  geo_point = Types::GeoPoint.new(options)
40
40
  end
41
-
42
41
  builder_from_options(options).add_geo(field, distance, geo_point)
43
42
  end
44
43
 
@@ -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] => :where
25
- delegate [:fulltext] => :match
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, options)
32
+ # @overload initialize(base_or_opts, params)
33
33
  # @param base [Base] another clause to copy attributes from
34
- # @param options [Hash] options to set on the new state
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, options = {})
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, options = {})
105
- base.limit = options[:limit] || options[:per_page] || get_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
- # Used for fulltext searching. Works similarly
165
- # to {#where} .
166
- #
167
- # @param options = {} [Hash] Options to be passed to
168
- # the MatchClause
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(opts_or_string)
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(opts_or_string)
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(opts_or_string = {})
255
- if opts_or_string.is_a?(Hash)
256
- WhereClause.new(base).should(opts_or_string)
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(opts_or_string)
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 opts = {} [Hash] JSON to aggregate on
219
+ # @param params = {} [Hash] JSON to aggregate on
266
220
  #
267
221
  # @return [self] Allows continuing the query chain
268
- def aggregations(opts = {})
269
- base.aggregate_builder = base.aggregate_builder.merge(opts)
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 options = {} [Hash] options for full text matching
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 options = {} [Hash] Filters to use in this boost.
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 options [Numeric] :field What field to check with this boost
80
- # @option options [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)
81
- # @option options [Numeric] :lat Latitude, for a geo point
82
- # @option options [Numeric] :latitude Latitude, for a geo point
83
- # @option options [Numeric] :lng Longitude, for a geo point
84
- # @option options [Numeric] :lon Longitude, for a geo point
85
- # @option options [Numeric] :longitude Longitude, for a geo point
86
- # @option options [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
87
- # @option options [String] :offset Anything within this distance of the origin is boosted as if it were at the origin
88
- # @option options [Symbol] :type (:gauss) What type of decay to use. One of `:linear`, `:exp`, or `:gauss`
89
- # @option options [Numeric] :decay_amount (0.5) How much the boost falls off when it is `:scale` distance from `:origin`
90
- # @option options [Numeric] :weight (1.2) How strongly to weight this boost compared to others
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 options)
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 options[:lat] || options[:latitude] ||
124
- options[:lng] || options[:longitude] || options[:lon]
144
+ def near(params = {}, options = {})
145
+ if params[:lat] || params[:latitude] ||
146
+ params[:lng] || params[:longitude] || params[:lon]
125
147
 
126
- options[:origin] = Stretchy::Types::GeoPoint.new(options)
148
+ params[:origin] = Stretchy::Types::GeoPoint.new(params)
127
149
  end
128
- base.boost_builder.functions << Stretchy::Boosts::FieldDecayBoost.new(options)
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(opts_or_string)
23
+ # @overload not(params)
45
24
  # @param [String] String that must not match anywhere in the document
46
25
  #
47
- # @overload not(opts_or_string)
48
- # @param opts_or_string [Hash] Fields and values that should not match in the document
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(opts_or_string = {})
30
+ def not(params = {})
52
31
  @inverse = true
53
- if opts_or_string.is_a?(Hash)
54
- match_function(opts_or_string)
55
- else
56
- match_function('_all' => opts_or_string)
57
- end
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, *args)
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, *args)
82
+ MatchClause.new(base).match(*args)
93
83
  end
94
84
 
95
85
  private
96
86
 
97
- def match_function(options = {})
98
- weight = options.delete(:weight)
99
- clause = MatchClause.tmp(options)
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.functions << boost if boost
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
- # Generates a boost that matches a set of filters.
16
- #
17
- # @param base [Base] Query to copy data from.
18
- # @param options = {} [Hash] Fields and values to filter on.
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, *args)
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, *args)
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(*args)
75
- where_function(:range, *args)
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(*args)
95
- where_function(:geo, *args)
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
- # Creates a temporary MatchClause outside the main
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
- # Creates a new state with a match query applied.
33
- #
34
- # @overload initialize(base, opts_or_string)
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(opts_or_str)
83
- # @param opts_or_str = {} [String] A phrase that will
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(opts_or_str)
87
- # @param opts_or_str = {} [Hash] A hash of fields and phrases
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(opts_or_str = {})
100
- add_params(opts_or_str, min: 1)
101
- add_params(opts_or_str, should: true, slop: 50)
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(opts_or_str)
66
+ # @overload not(params)
110
67
  # @param [String] A string that must not be matched anywhere in the document
111
- # @overload not(opts_or_str)
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(opts_or_str = {})
81
+ def not(params = {}, options = {})
125
82
  @inverse = true
126
- add_params(opts_or_str)
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(opts_or_str)
94
+ # @overload should(params)
138
95
  # @param [String] A string that should be matched anywhere in the document
139
- # @overload should(opts_or_str)
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(opts_or_str = {})
117
+ def should(params = {}, options = {})
161
118
  @should = true
162
119
  @inverse = false
163
- add_params(opts_or_str)
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 options = {} [Hash] filters to be applied to the new state
48
- # @option options [true, false] :inverted (nil) Whether the new state is inverted
49
- # @option options [true, false] :should (nil) Whether the new state is should
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 initialize(base = nil, options = {})
66
- super(base)
67
- add_params(options)
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
- add_params(params, exact: true)
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 options = {} [Hash] Options to filter on
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, "options"]
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 options = {} [Hash] Options to filter on
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 options
213
+ # @example Specifying should params
226
214
  # query.should(
227
215
  # field: [99, 27]
228
216
  # )
229
217
  #
230
- # @example Inverted should options
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
 
@@ -0,0 +1,6 @@
1
+ module Stretchy
2
+ module Errors
3
+ class InvalidQueryError < StandardError
4
+ end
5
+ end
6
+ end
@@ -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'
@@ -1,3 +1,3 @@
1
1
  module Stretchy
2
- VERSION = "0.4.2"
2
+ VERSION = "0.4.3"
3
3
  end
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.2
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-04 00:00:00.000000000 Z
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