chewy_query 0.0.1

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.
Files changed (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +22 -0
  3. data/.rspec +3 -0
  4. data/Gemfile +6 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +37 -0
  7. data/Rakefile +2 -0
  8. data/chewy_query.gemspec +27 -0
  9. data/lib/chewy_query.rb +12 -0
  10. data/lib/chewy_query/builder.rb +865 -0
  11. data/lib/chewy_query/builder/compose.rb +64 -0
  12. data/lib/chewy_query/builder/criteria.rb +182 -0
  13. data/lib/chewy_query/builder/filters.rb +227 -0
  14. data/lib/chewy_query/builder/nodes/and.rb +26 -0
  15. data/lib/chewy_query/builder/nodes/base.rb +17 -0
  16. data/lib/chewy_query/builder/nodes/bool.rb +33 -0
  17. data/lib/chewy_query/builder/nodes/equal.rb +34 -0
  18. data/lib/chewy_query/builder/nodes/exists.rb +20 -0
  19. data/lib/chewy_query/builder/nodes/expr.rb +28 -0
  20. data/lib/chewy_query/builder/nodes/field.rb +106 -0
  21. data/lib/chewy_query/builder/nodes/has_child.rb +14 -0
  22. data/lib/chewy_query/builder/nodes/has_parent.rb +14 -0
  23. data/lib/chewy_query/builder/nodes/has_relation.rb +61 -0
  24. data/lib/chewy_query/builder/nodes/match_all.rb +11 -0
  25. data/lib/chewy_query/builder/nodes/missing.rb +20 -0
  26. data/lib/chewy_query/builder/nodes/not.rb +26 -0
  27. data/lib/chewy_query/builder/nodes/or.rb +26 -0
  28. data/lib/chewy_query/builder/nodes/prefix.rb +18 -0
  29. data/lib/chewy_query/builder/nodes/query.rb +20 -0
  30. data/lib/chewy_query/builder/nodes/range.rb +63 -0
  31. data/lib/chewy_query/builder/nodes/raw.rb +15 -0
  32. data/lib/chewy_query/builder/nodes/regexp.rb +33 -0
  33. data/lib/chewy_query/builder/nodes/script.rb +20 -0
  34. data/lib/chewy_query/version.rb +3 -0
  35. data/spec/chewy_query/builder/context_spec.rb +529 -0
  36. data/spec/chewy_query/builder/filters_spec.rb +181 -0
  37. data/spec/chewy_query/builder/nodes/and_spec.rb +16 -0
  38. data/spec/chewy_query/builder/nodes/bool_spec.rb +22 -0
  39. data/spec/chewy_query/builder/nodes/equal_spec.rb +58 -0
  40. data/spec/chewy_query/builder/nodes/exists_spec.rb +16 -0
  41. data/spec/chewy_query/builder/nodes/has_child_spec.rb +79 -0
  42. data/spec/chewy_query/builder/nodes/has_parent_spec.rb +84 -0
  43. data/spec/chewy_query/builder/nodes/match_all_spec.rb +11 -0
  44. data/spec/chewy_query/builder/nodes/missing_spec.rb +14 -0
  45. data/spec/chewy_query/builder/nodes/not_spec.rb +14 -0
  46. data/spec/chewy_query/builder/nodes/or_spec.rb +16 -0
  47. data/spec/chewy_query/builder/nodes/prefix_spec.rb +15 -0
  48. data/spec/chewy_query/builder/nodes/query_spec.rb +17 -0
  49. data/spec/chewy_query/builder/nodes/range_spec.rb +36 -0
  50. data/spec/chewy_query/builder/nodes/raw_spec.rb +11 -0
  51. data/spec/chewy_query/builder/nodes/regexp_spec.rb +45 -0
  52. data/spec/chewy_query/builder/nodes/script_spec.rb +16 -0
  53. data/spec/chewy_query/builder_spec.rb +196 -0
  54. data/spec/chewy_query_spec.rb +0 -0
  55. data/spec/spec_helper.rb +8 -0
  56. metadata +191 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 50cc4c9404f4fec945f7e1de80998603db979fe3
4
+ data.tar.gz: a4507b1b3aefe9a7924dcb487d248c4bac2de514
5
+ SHA512:
6
+ metadata.gz: 0258c2c32f63a25f7e249b30b0dcf878263d3a78eea13b34dfed4b3bed4a835a9205edd954c0e4053cda2642d55618e2956f4ab6b6e96b695aafdd55639c58ff
7
+ data.tar.gz: 6c84d4f21a27d42e1439b49e24749ff1f4cecabaf4c32fb0ef734aaa813a073fa5faeaef220ec2d40d61cc8ee0e996e51ec853cca9f550967e80e6e2d0d7cd6c
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --color
2
+ --backtrace
3
+ --order random
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in chewy_query.gemspec
4
+ gemspec
5
+
6
+ gem 'pry'
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 undr
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,37 @@
1
+ # ChewyQuery
2
+
3
+ The query builder for ElasticSearch which was extracted from [chewy](https://github.com/toptal/chewy) gem.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'chewy_query'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ ```
16
+ $ bundle
17
+ ```
18
+
19
+ Or install it yourself as:
20
+
21
+ ```
22
+ $ gem install chewy_query
23
+ ```
24
+
25
+ ## Usage
26
+
27
+ See [chewy querying](https://github.com/toptal/chewy#index-querying).
28
+
29
+ Many thanks to @pyromaniac for his [chewy](https://github.com/toptal/chewy).
30
+
31
+ ## Contributing
32
+
33
+ 1. Fork it ( https://github.com/undr/chewy_query/fork )
34
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
35
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
36
+ 4. Push to the branch (`git push origin my-new-feature`)
37
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'chewy_query/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'chewy_query'
8
+ spec.version = ChewyQuery::VERSION
9
+ spec.authors = ['pyromaniac', 'undr']
10
+ spec.email = ['kinwizard@gmail.com', 'undr@yandex.ru']
11
+ spec.summary = %q{The query builder for ElasticSearch which was extracted from Chewy.}
12
+ spec.description = %q{The query builder for ElasticSearch which was extracted from Chewy.}
13
+ spec.homepage = ''
14
+ spec.license = 'MIT'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_dependency 'activesupport', '>= 3.2'
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.6'
24
+ spec.add_development_dependency 'rake'
25
+ spec.add_development_dependency 'rspec'
26
+ spec.add_development_dependency 'its'
27
+ end
@@ -0,0 +1,12 @@
1
+ require 'active_support'
2
+ require 'active_support/core_ext'
3
+ require 'active_support/concern'
4
+ require 'active_support/json'
5
+ require 'i18n/core_ext/hash'
6
+
7
+ require "chewy_query/version"
8
+ require 'chewy_query/builder'
9
+
10
+ module ChewyQuery
11
+
12
+ end
@@ -0,0 +1,865 @@
1
+ require 'chewy_query/builder/criteria'
2
+ require 'chewy_query/builder/filters'
3
+
4
+ module ChewyQuery
5
+ # Builder allows you to create ES search requests with convenient
6
+ # chainable DSL. Queries are lazy evaluated and might be merged.
7
+ #
8
+ # builder = ChewyQuery::Builder.new(:users, types: ['admin', 'manager', 'user'])
9
+ # builder.filter{ age < 42 }.query(text: {name: 'Alex'}).limit(20)
10
+ # builder = ChewyQuery::Builder.new(:users, types: 'admin')
11
+ # builder.filter{ age < 42 }.query(text: {name: 'Alex'}).limit(20)
12
+ #
13
+ class Builder
14
+ attr_reader :index, :options, :criteria
15
+
16
+ def initialize(index, options = {})
17
+ @index, @options = index, options
18
+ @types = Array.wrap(options.delete(:types))
19
+ @criteria = Criteria.new(options)
20
+ reset
21
+ end
22
+
23
+ # Comparation with other query or collection
24
+ # If other is collection - search request is executed and
25
+ # result is used for comparation
26
+ #
27
+ # builder.filter(term: {name: 'Johny'}) == builder.filter(term: {name: 'Johny'}) # => true
28
+ # builder.filter(term: {name: 'Johny'}) == builder.filter(term: {name: 'Johny'}).to_a # => true
29
+ # builder.filter(term: {name: 'Johny'}) == builder.filter(term: {name: 'Winnie'}) # => false
30
+ #
31
+ def ==(other)
32
+ super || if other.is_a?(self.class)
33
+ other.criteria == criteria
34
+ else
35
+ to_a == other
36
+ end
37
+ end
38
+
39
+ # Adds <tt>explain</tt> parameter to search request.
40
+ #
41
+ # builder.filter(term: {name: 'Johny'}).explain
42
+ # builder.filter(term: {name: 'Johny'}).explain(true)
43
+ # builder.filter(term: {name: 'Johny'}).explain(false)
44
+ #
45
+ # Calling explain without any arguments sets explanation flag to true.
46
+ #
47
+ # builder.filter(term: {name: 'Johny'}).explain
48
+ #
49
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-explain.html
50
+ #
51
+ def explain(value = nil)
52
+ chain{ criteria.update_request_options explain: (value.nil? ? true : value) }
53
+ end
54
+
55
+ # Adds <tt>version</tt> parameter to search request.
56
+ #
57
+ # builder.filter(term: {name: 'Johny'}).version
58
+ # builder.filter(term: {name: 'Johny'}).version(true)
59
+ # builder.filter(term: {name: 'Johny'}).version(false)
60
+ #
61
+ # Calling version without any arguments sets version flag to true.
62
+ #
63
+ # builder.filter(term: {name: 'Johny'}).version
64
+ #
65
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-version.html
66
+ #
67
+ def version(value = nil)
68
+ chain{ criteria.update_request_options version: (value.nil? ? true : value) }
69
+ end
70
+
71
+ # Sets query compilation mode for search request.
72
+ # Not used if only one filter for search is specified.
73
+ # Possible values:
74
+ #
75
+ # * <tt>:must</tt>
76
+ # Default value. Query compiles into a bool <tt>must</tt> query.
77
+ #
78
+ # Ex:
79
+ #
80
+ # builder.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}})
81
+ # # => {body: {
82
+ # query: {bool: {must: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
83
+ # }}
84
+ #
85
+ # * <tt>:should</tt>
86
+ # Query compiles into a bool <tt>should</tt> query.
87
+ #
88
+ # Ex:
89
+ #
90
+ # builder.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode(:should)
91
+ # # => {body: {
92
+ # query: {bool: {should: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
93
+ # }}
94
+ #
95
+ # * Any acceptable <tt>minimum_should_match</tt> value (1, '2', '75%')
96
+ # Query compiles into a bool <tt>should</tt> query with <tt>minimum_should_match</tt> set.
97
+ #
98
+ # Ex:
99
+ #
100
+ # builder.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode('50%')
101
+ # # => {body: {
102
+ # query: {bool: {
103
+ # should: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}],
104
+ # minimum_should_match: '50%'
105
+ # }}
106
+ # }}
107
+ #
108
+ # * <tt>:dis_max</tt>
109
+ # Query compiles into a <tt>dis_max</tt> query.
110
+ #
111
+ # Ex:
112
+ #
113
+ # builder.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode(:dis_max)
114
+ # # => {body: {
115
+ # query: {dis_max: {queries: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
116
+ # }}
117
+ #
118
+ # * Any Float value (0.0, 0.7, 1.0)
119
+ # Query compiles into a <tt>dis_max</tt> query with <tt>tie_breaker</tt> option set.
120
+ #
121
+ # Ex:
122
+ #
123
+ # builder.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode(0.7)
124
+ # # => {body: {
125
+ # query: {dis_max: {
126
+ # queries: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}],
127
+ # tie_breaker: 0.7
128
+ # }}
129
+ # }}
130
+ #
131
+ # Default value for <tt>:query_mode</tt> might be changed
132
+ # with <tt>ChewyQuery.query_mode</tt> config option.
133
+ #
134
+ # ChewyQuery.query_mode = :dis_max
135
+ # ChewyQuery.query_mode = '50%'
136
+ #
137
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-dis-max-query.html
138
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-bool-query.html
139
+ #
140
+ def query_mode(value)
141
+ chain{ criteria.update_options query_mode: value }
142
+ end
143
+
144
+ # Sets query compilation mode for search request.
145
+ # Not used if only one filter for search is specified.
146
+ # Possible values:
147
+ #
148
+ # * <tt>:and</tt>
149
+ # Default value. Filter compiles into an <tt>and</tt> filter.
150
+ #
151
+ # Ex:
152
+ #
153
+ # builder.filter{ name == 'Johny' }.filter{ age <= 42 }
154
+ # # => {body: {query: {filtered: {
155
+ # query: {...},
156
+ # filter: {and: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
157
+ # }}}}
158
+ #
159
+ # * <tt>:or</tt>
160
+ # Filter compiles into an <tt>or</tt> filter.
161
+ #
162
+ # Ex:
163
+ #
164
+ # builder.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode(:or)
165
+ # # => {body: {query: {filtered: {
166
+ # query: {...},
167
+ # filter: {or: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
168
+ # }}}}
169
+ #
170
+ # * <tt>:must</tt>
171
+ # Filter compiles into a bool <tt>must</tt> filter.
172
+ #
173
+ # Ex:
174
+ #
175
+ # builder.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode(:must)
176
+ # # => {body: {query: {filtered: {
177
+ # query: {...},
178
+ # filter: {bool: {must: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
179
+ # }}}}
180
+ #
181
+ # * <tt>:should</tt>
182
+ # Filter compiles into a bool <tt>should</tt> filter.
183
+ #
184
+ # Ex:
185
+ #
186
+ # builder.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode(:should)
187
+ # # => {body: {query: {filtered: {
188
+ # query: {...},
189
+ # filter: {bool: {should: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
190
+ # }}}}
191
+ #
192
+ # * Any acceptable <tt>minimum_should_match</tt> value (1, '2', '75%')
193
+ # Filter compiles into bool <tt>should</tt> filter with <tt>minimum_should_match</tt> set.
194
+ #
195
+ # Ex:
196
+ #
197
+ # builder.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode('50%')
198
+ # # => {body: {query: {filtered: {
199
+ # query: {...},
200
+ # filter: {bool: {
201
+ # should: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}],
202
+ # minimum_should_match: '50%'
203
+ # }}
204
+ # }}}}
205
+ #
206
+ # Default value for <tt>:filter_mode</tt> might be changed
207
+ # with <tt>ChewyQuery.filter_mode</tt> config option.
208
+ #
209
+ # ChewyQuery.filter_mode = :should
210
+ # ChewyQuery.filter_mode = '50%'
211
+ #
212
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-and-filter.html
213
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-bool-filter.html
214
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-or-filter.html
215
+ #
216
+ def filter_mode(value)
217
+ chain{ criteria.update_options filter_mode: value }
218
+ end
219
+
220
+ # Acts the same way as `filter_mode`, but used for `post_filter`.
221
+ # Note that it fallbacks by default to `ChewyQuery.filter_mode` if
222
+ # `ChewyQuery.post_filter_mode` is nil.
223
+ #
224
+ # builder.post_filter{ name == 'Johny' }.post_filter{ age <= 42 }.post_filter_mode(:and)
225
+ # builder.post_filter{ name == 'Johny' }.post_filter{ age <= 42 }.post_filter_mode(:should)
226
+ # builder.post_filter{ name == 'Johny' }.post_filter{ age <= 42 }.post_filter_mode('50%')
227
+ #
228
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-and-filter.html
229
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-bool-filter.html
230
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-or-filter.html
231
+ #
232
+ def post_filter_mode(value)
233
+ chain{ criteria.update_options post_filter_mode: value }
234
+ end
235
+
236
+ # Sets elasticsearch <tt>size</tt> search request param
237
+ # Default value is set in the elasticsearch and is 10.
238
+ #
239
+ # builder.filter{ name == 'Johny' }.limit(100)
240
+ # # => {body: {
241
+ # query: {...},
242
+ # size: 100
243
+ # }}
244
+ #
245
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-from-size.html
246
+ #
247
+ def limit(value)
248
+ chain{ criteria.update_request_options size: Integer(value) }
249
+ end
250
+
251
+ # Sets elasticsearch <tt>from</tt> search request param
252
+ #
253
+ # builder.filter{ name == 'Johny' }.offset(300)
254
+ # # => {body: {
255
+ # query: {...},
256
+ # from: 300
257
+ # }}
258
+ #
259
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-from-size.html
260
+ #
261
+ def offset(value)
262
+ chain{ criteria.update_request_options from: Integer(value) }
263
+ end
264
+
265
+ # Elasticsearch highlight query option support
266
+ #
267
+ # builder.query(...).highlight(fields: { ... })
268
+ #
269
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-highlighting.html
270
+ #
271
+ def highlight(value)
272
+ chain{ criteria.update_request_options highlight: value }
273
+ end
274
+
275
+ # Elasticsearch rescore query option support
276
+ #
277
+ # builder.query(...).rescore(query: { ... })
278
+ #
279
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-rescore.html
280
+ #
281
+ def rescore(value)
282
+ chain{ criteria.update_request_options(rescore: value) }
283
+ end
284
+
285
+ # Adds facets section to the search request.
286
+ # All the chained facets a merged and added to the
287
+ # search request
288
+ #
289
+ # builder.facets(tags: {terms: {field: 'tags'}}).facets(ages: {terms: {field: 'age'}})
290
+ # # => {body: {
291
+ # query: {...},
292
+ # facets: {tags: {terms: {field: 'tags'}}, ages: {terms: {field: 'age'}}}
293
+ # }}
294
+ #
295
+ # If called parameterless - returns result facets from ES performing request.
296
+ # Returns empty hash if no facets was requested or resulted.
297
+ #
298
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-facets.html
299
+ #
300
+ def facets(params)
301
+ chain{ criteria.update_facets(params) }
302
+ end
303
+
304
+ # Adds a script function to score the search request. All scores are
305
+ # added to the search request and combinded according to
306
+ # <tt>boost_mode</tt> and <tt>score_mode</tt>
307
+ #
308
+ # builder.script_score("doc['boost'].value", filter: { term: {foo: :bar} })
309
+ # # => {body:
310
+ # query: {
311
+ # function_score: {
312
+ # query: { ...},
313
+ # functions: [{
314
+ # script_score: {
315
+ # script: "doc['boost'].value"
316
+ # },
317
+ # filter: { term: { foo: :bar } }
318
+ # }
319
+ # }]
320
+ # } } }
321
+ #
322
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#_script_score
323
+ #
324
+ def script_score(script, options = {})
325
+ scoring = options.merge(script_score: { script: script })
326
+ chain{ criteria.update_scores(scoring) }
327
+ end
328
+
329
+ # Adds a boost factor to the search request. All scores are
330
+ # added to the search request and combinded according to
331
+ # <tt>boost_mode</tt> and <tt>score_mode</tt>
332
+ #
333
+ # This probably only makes sense if you specifiy a filter
334
+ # for the boost factor as well
335
+ #
336
+ # builder.boost_factor(23, filter: { term: { foo: :bar} })
337
+ # # => {body:
338
+ # query: {
339
+ # function_score: {
340
+ # query: { ...},
341
+ # functions: [{
342
+ # boost_factor: 23,
343
+ # filter: { term: { foo: :bar } }
344
+ # }]
345
+ # } } }
346
+ #
347
+ # @see
348
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#_boost_factor
349
+ #
350
+ def boost_factor(factor, options = {})
351
+ scoring = options.merge(boost_factor: factor.to_i)
352
+ chain{ criteria.update_scores(scoring) }
353
+ end
354
+
355
+ # Adds a random score to the search request. All scores are
356
+ # added to the search request and combinded according to
357
+ # <tt>boost_mode</tt> and <tt>score_mode</tt>
358
+ #
359
+ # This probably only makes sense if you specifiy a filter
360
+ # for the random score as well.
361
+ #
362
+ # If you do not pass in a seed value, Time.now will be used
363
+ #
364
+ # builder.random_score(23, filter: { foo: :bar})
365
+ # # => {body:
366
+ # query: {
367
+ # function_score: {
368
+ # query: { ...},
369
+ # functions: [{
370
+ # random_score: { seed: 23 },
371
+ # filter: { foo: :bar }
372
+ # }]
373
+ # } } }
374
+ #
375
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#_random
376
+ #
377
+ def random_score(seed = Time.now, options = {})
378
+ scoring = options.merge(random_score: { seed: seed.to_i })
379
+ chain{ criteria.update_scores(scoring) }
380
+ end
381
+
382
+ # Add a field value scoring to the search. All scores are
383
+ # added to the search request and combinded according to
384
+ # <tt>boost_mode</tt> and <tt>score_mode</tt>
385
+ #
386
+ # This function is only available in Elasticsearch 1.2 and
387
+ # greater
388
+ #
389
+ # builder.field_value_factor(
390
+ # {
391
+ # field: :boost,
392
+ # factor: 1.2,
393
+ # modifier: :sqrt
394
+ # }, filter: { foo: :bar})
395
+ # # => {body:
396
+ # query: {
397
+ # function_score: {
398
+ # query: { ...},
399
+ # functions: [{
400
+ # field_value_factor: {
401
+ # field: :boost,
402
+ # factor: 1.2,
403
+ # modifier: :sqrt
404
+ # },
405
+ # filter: { foo: :bar }
406
+ # }]
407
+ # } } }
408
+ #
409
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#_field_value_factor
410
+ #
411
+ def field_value_factor(settings, options = {})
412
+ scoring = options.merge(field_value_factor: settings)
413
+ chain{ criteria.update_scores(scoring) }
414
+ end
415
+
416
+ # Add a decay scoring to the search. All scores are
417
+ # added to the search request and combinded according to
418
+ # <tt>boost_mode</tt> and <tt>score_mode</tt>
419
+ #
420
+ # The parameters have default values, but those may not
421
+ # be very useful for most applications.
422
+ #
423
+ # builder.decay(
424
+ # :gauss,
425
+ # :field,
426
+ # origin: '11, 12',
427
+ # scale: '2km',
428
+ # offset: '5km'
429
+ # decay: 0.4
430
+ # filter: { foo: :bar})
431
+ # # => {body:
432
+ # query: {
433
+ # gauss: {
434
+ # query: { ...},
435
+ # functions: [{
436
+ # gauss: {
437
+ # field: {
438
+ # origin: '11, 12',
439
+ # scale: '2km',
440
+ # offset: '5km',
441
+ # decay: 0.4
442
+ # }
443
+ # },
444
+ # filter: { foo: :bar }
445
+ # }]
446
+ # } } }
447
+ #
448
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html#_decay_functions
449
+ #
450
+ def decay(function, field, options = {})
451
+ field_options = {
452
+ origin: options.delete(:origin) || 0,
453
+ scale: options.delete(:scale) || 1,
454
+ offset: options.delete(:offset) || 0,
455
+ decay: options.delete(:decay) || 0.1
456
+ }
457
+ scoring = options.merge(function => {
458
+ field => field_options
459
+ })
460
+ chain{ criteria.update_scores(scoring) }
461
+ end
462
+
463
+ # Sets elasticsearch <tt>aggregations</tt> search request param
464
+ #
465
+ # builder.filter{ name == 'Johny' }.aggregations(category_id: {terms: {field: 'category_ids'}})
466
+ # # => {body: {
467
+ # query: {...},
468
+ # aggregations: {
469
+ # terms: {
470
+ # field: 'category_ids'
471
+ # }
472
+ # }
473
+ # }}
474
+ #
475
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-aggregations.html
476
+ #
477
+ def aggregations(params = nil)
478
+ chain{ criteria.update_aggregations params }
479
+ end
480
+ alias :aggs :aggregations
481
+
482
+ # Sets elasticsearch <tt>suggest</tt> search request param
483
+ #
484
+ # builder.suggest(name: {text: 'Joh', term: {field: 'name'}})
485
+ # # => {body: {
486
+ # query: {...},
487
+ # suggest: {
488
+ # text: 'Joh',
489
+ # term: {
490
+ # field: 'name'
491
+ # }
492
+ # }
493
+ # }}
494
+ #
495
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-suggesters.html
496
+ #
497
+ def suggest(params = nil)
498
+ chain{ criteria.update_suggest params }
499
+ end
500
+
501
+ # Marks the criteria as having zero records. This scope always returns empty array
502
+ # without touching the elasticsearch server.
503
+ # All the chained calls of methods don't affect the result
504
+ #
505
+ # UsersIndex.none.to_a
506
+ # # => []
507
+ # UsersIndex.query(text: {name: 'Johny'}).none.to_a
508
+ # # => []
509
+ # UsersIndex.none.query(text: {name: 'Johny'}).to_a
510
+ # # => []
511
+ def none
512
+ chain{ criteria.update_options(none: true) }
513
+ end
514
+
515
+ # Setups strategy for top-level filtered query
516
+ #
517
+ # builder.filter { name == 'Johny'}.strategy(:leap_frog)
518
+ # # => {body: {
519
+ # query: { filtered: {
520
+ # filter: { term: { name: 'Johny' } },
521
+ # strategy: 'leap_frog'
522
+ # } }
523
+ # }}
524
+ #
525
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-filtered-query.html#_filter_strategy
526
+ #
527
+ def strategy(value = nil)
528
+ chain{ criteria.update_options(strategy: value) }
529
+ end
530
+
531
+ # Adds one or more query to the search request
532
+ # Internally queries are stored as an array
533
+ # While the full query compilation this array compiles
534
+ # according to <tt>:query_mode</tt> option value
535
+ #
536
+ # By default it joines inside <tt>must</tt> query
537
+ # See <tt>#query_mode</tt> chainable method for more info.
538
+ #
539
+ # builder.query(match: {name: 'Johny'}).query(range: {age: {lte: 42}})
540
+ # # => {body: {
541
+ # query: {bool: {must: [{match: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
542
+ # }}
543
+ #
544
+ # If only one query was specified, it will become a result
545
+ # query as is, without joining.
546
+ #
547
+ # builder.query(match: {name: 'Johny'})
548
+ # # => {body: {
549
+ # query: {match: {name: 'Johny'}}
550
+ # }}
551
+ #
552
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-queries.html
553
+ #
554
+ def query(params)
555
+ chain{ criteria.update_queries(params) }
556
+ end
557
+
558
+ # Adds one or more filter to the search request
559
+ # Internally filters are stored as an array
560
+ # While the full query compilation this array compiles
561
+ # according to <tt>:filter_mode</tt> option value
562
+ #
563
+ # By default it joins inside <tt>and</tt> filter
564
+ # See <tt>#filter_mode</tt> chainable method for more info.
565
+ #
566
+ # Also this method supports block DSL.
567
+ # See <tt>ChewyQuery::Builder::Filters</tt> for more info.
568
+ #
569
+ # builder.filter(term: {name: 'Johny'}).filter(range: {age: {lte: 42}})
570
+ # builder.filter{ name == 'Johny' }.filter{ age <= 42 }
571
+ # # => {body: {query: {filtered: {
572
+ # query: {...},
573
+ # filter: {and: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
574
+ # }}}}
575
+ #
576
+ # If only one filter was specified, it will become a result
577
+ # filter as is, without joining.
578
+ #
579
+ # builder.filter(term: {name: 'Johny'})
580
+ # # => {body: {query: {filtered: {
581
+ # query: {...},
582
+ # filter: {term: {name: 'Johny'}}
583
+ # }}}}
584
+ #
585
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-filters.html
586
+ #
587
+ def filter(params = nil, &block)
588
+ params = Filters.new(&block).__render__ if block
589
+ chain{ criteria.update_filters(params) }
590
+ end
591
+
592
+ # Adds one or more post_filter to the search request
593
+ # Internally post_filters are stored as an array
594
+ # While the full query compilation this array compiles
595
+ # according to <tt>:post_filter_mode</tt> option value
596
+ #
597
+ # By default it joins inside <tt>and</tt> filter
598
+ # See <tt>#post_filter_mode</tt> chainable method for more info.
599
+ #
600
+ # Also this method supports block DSL.
601
+ # See <tt>ChewyQuery::Builder::Filters</tt> for more info.
602
+ #
603
+ # builder.post_filter(term: {name: 'Johny'}).post_filter(range: {age: {lte: 42}})
604
+ # builder.post_filter{ name == 'Johny' }.post_filter{ age <= 42 }
605
+ # # => {body: {
606
+ # post_filter: {and: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
607
+ # }}
608
+ #
609
+ # If only one post_filter was specified, it will become a result
610
+ # post_filter as is, without joining.
611
+ #
612
+ # builder.post_filter(term: {name: 'Johny'})
613
+ # # => {body: {
614
+ # post_filter: {term: {name: 'Johny'}}
615
+ # }}
616
+ #
617
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-post-filter.html
618
+ #
619
+ def post_filter(params = nil, &block)
620
+ params = Filters.new(&block).__render__ if block
621
+ chain{ criteria.update_post_filters(params) }
622
+ end
623
+
624
+ # Sets the boost mode for custom scoring/boosting.
625
+ # Not used if no score functions are specified
626
+ # Possible values:
627
+ #
628
+ # * <tt>:multiply</tt>
629
+ # Default value. Query score and function result are multiplied.
630
+ #
631
+ # Ex:
632
+ #
633
+ # builder.boost_mode('multiply').script_score('doc['boost'].value')
634
+ # # => {body: {query: function_score: {
635
+ # query: {...},
636
+ # boost_mode: 'multiply',
637
+ # functions: [ ... ]
638
+ # }}}
639
+ #
640
+ # * <tt>:replace</tt>
641
+ # Only function result is used, query score is ignored.
642
+ #
643
+ # * <tt>:sum</tt>
644
+ # Query score and function score are added.
645
+ #
646
+ # * <tt>:avg</tt>
647
+ # Average of query and function score.
648
+ #
649
+ # * <tt>:max</tt>
650
+ # Max of query and function score.
651
+ #
652
+ # * <tt>:min</tt>
653
+ # Min of query and function score.
654
+ #
655
+ # Default value for <tt>:boost_mode</tt> might be changed
656
+ # with <tt>ChewyQuery.score_mode</tt> config option.
657
+ #
658
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html
659
+ #
660
+ def boost_mode(value)
661
+ chain{ criteria.update_options(boost_mode: value) }
662
+ end
663
+
664
+ # Sets the scoring mode for combining function scores/boosts
665
+ # Not used if no score functions are specified.
666
+ # Possible values:
667
+ #
668
+ # * <tt>:multiply</tt>
669
+ # Default value. Scores are multiplied.
670
+ #
671
+ # Ex:
672
+ #
673
+ # builder.score_mode('multiply').script_score('doc['boost'].value')
674
+ # # => {body: {query: function_score: {
675
+ # query: {...},
676
+ # score_mode: 'multiply',
677
+ # functions: [ ... ]
678
+ # }}}
679
+ #
680
+ # * <tt>:sum</tt>
681
+ # Scores are summed.
682
+ #
683
+ # * <tt>:avg</tt>
684
+ # Scores are averaged.
685
+ #
686
+ # * <tt>:first</tt>
687
+ # The first function that has a matching filter is applied.
688
+ #
689
+ # * <tt>:max</tt>
690
+ # Maximum score is used.
691
+ #
692
+ # * <tt>:min</tt>
693
+ # Minimum score is used
694
+ #
695
+ # Default value for <tt>:score_mode</tt> might be changed
696
+ # with <tt>ChewyQuery.score_mode</tt> config option.
697
+ #
698
+ # ChewyQuery.score_mode = :first
699
+ #
700
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-function-score-query.html
701
+ #
702
+ def score_mode(value)
703
+ chain{ criteria.update_options(score_mode: value) }
704
+ end
705
+
706
+ # Sets search request sorting
707
+ #
708
+ # builder.order(:first_name, :last_name).order(age: :desc).order(price: {order: :asc, mode: :avg})
709
+ # # => {body: {
710
+ # query: {...},
711
+ # sort: ['first_name', 'last_name', {age: 'desc'}, {price: {order: 'asc', mode: 'avg'}}]
712
+ # }}
713
+ #
714
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html
715
+ #
716
+ def order(*params)
717
+ chain{ criteria.update_sort(params) }
718
+ end
719
+
720
+ # Cleans up previous search sorting and sets the new one
721
+ #
722
+ # builder.order(:first_name, :last_name).order(age: :desc).reorder(price: {order: :asc, mode: :avg})
723
+ # # => {body: {
724
+ # query: {...},
725
+ # sort: [{price: {order: 'asc', mode: 'avg'}}]
726
+ # }}
727
+ #
728
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-sort.html
729
+ #
730
+ def reorder(*params)
731
+ chain{ criteria.update_sort(params, purge: true) }
732
+ end
733
+
734
+ # Sets search request field list
735
+ #
736
+ # builder.only(:first_name, :last_name).only(:age)
737
+ # # => {body: {
738
+ # query: {...},
739
+ # fields: ['first_name', 'last_name', 'age']
740
+ # }}
741
+ #
742
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-fields.html
743
+ #
744
+ def only(*params)
745
+ chain{ criteria.update_fields(params) }
746
+ end
747
+
748
+ # Cleans up previous search field list and sets the new one
749
+ #
750
+ # builder.only(:first_name, :last_name).only!(:age)
751
+ # # => {body: {
752
+ # query: {...},
753
+ # fields: ['age']
754
+ # }}
755
+ #
756
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-request-fields.html
757
+ #
758
+ def only!(*params)
759
+ chain{ criteria.update_fields(params, purge: true) }
760
+ end
761
+
762
+ # Specify types participating in the search result
763
+ # Works via <tt>types</tt> filter. Always merged with another filters
764
+ # with the <tt>and</tt> filter.
765
+ #
766
+ # builder.types(:admin, :manager).filters{ name == 'Johny' }.filters{ age <= 42 }
767
+ # # => {body: {query: {filtered: {
768
+ # query: {...},
769
+ # filter: {and: [
770
+ # {or: [
771
+ # {type: {value: 'admin'}},
772
+ # {type: {value: 'manager'}}
773
+ # ]},
774
+ # {term: {name: 'Johny'}},
775
+ # {range: {age: {lte: 42}}}
776
+ # ]}
777
+ # }}}}
778
+ #
779
+ # builder.types(:admin, :manager).filters{ name == 'Johny' }.filters{ age <= 42 }.filter_mode(:or)
780
+ # # => {body: {query: {filtered: {
781
+ # query: {...},
782
+ # filter: {and: [
783
+ # {or: [
784
+ # {type: {value: 'admin'}},
785
+ # {type: {value: 'manager'}}
786
+ # ]},
787
+ # {or: [
788
+ # {term: {name: 'Johny'}},
789
+ # {range: {age: {lte: 42}}}
790
+ # ]}
791
+ # ]}
792
+ # }}}}
793
+ #
794
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-type-filter.html
795
+ #
796
+ def types(*params)
797
+ if params.any?
798
+ chain{ criteria.update_types(params) }
799
+ else
800
+ @types
801
+ end
802
+ end
803
+
804
+ # Acts the same way as <tt>types</tt>, but cleans up previously set types
805
+ #
806
+ # builder.types(:admin).types!(:manager)
807
+ # # => {body: {query: {filtered: {
808
+ # query: {...},
809
+ # filter: {type: {value: 'manager'}}
810
+ # }}}}
811
+ #
812
+ # @see http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/query-dsl-type-filter.html
813
+ #
814
+ def types!(*params)
815
+ chain{ criteria.update_types(params, purge: true) }
816
+ end
817
+
818
+ # Merges two queries.
819
+ # Merges all the values in criteria with the same rules as values added manually.
820
+ #
821
+ # scope1 = builder.filter{ name == 'Johny' }
822
+ # scope2 = builder.filter{ age <= 42 }
823
+ # scope3 = builder.filter{ name == 'Johny' }.filter{ age <= 42 }
824
+ #
825
+ # scope1.merge(scope2) == scope3 # => true
826
+ #
827
+ def merge(other)
828
+ chain{ criteria.merge!(other.criteria) }
829
+ end
830
+
831
+ def delete_all_request
832
+ @delete_all_request ||= criteria.delete_all_request_body.merge(index: index_name, type: types)
833
+ end
834
+
835
+
836
+ def request
837
+ @request ||= criteria.request_body.merge(index: index_name, type: types)
838
+ end
839
+
840
+ def inspect
841
+ "#<%s:%#016x @request=%s>" % [self.class, (object_id << 1), request]
842
+ end
843
+
844
+ protected
845
+
846
+ def reset
847
+ @request, @delete_all_request = nil
848
+ end
849
+
850
+ def initialize_clone(other)
851
+ @criteria = other.criteria.clone
852
+ reset
853
+ end
854
+
855
+ private
856
+
857
+ def index_name
858
+ index.respond_to?(:index_name) ? index.index_name : index
859
+ end
860
+
861
+ def chain(&block)
862
+ clone.tap{|q| q.instance_eval(&block) }
863
+ end
864
+ end
865
+ end