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 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