search_flip 3.0.0.beta2 → 3.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -0
- data/.travis.yml +3 -1
- data/CHANGELOG.md +7 -0
- data/README.md +33 -1
- data/lib/search_flip.rb +8 -0
- data/lib/search_flip/aggregatable.rb +9 -1
- data/lib/search_flip/aggregation.rb +30 -5
- data/lib/search_flip/bulk.rb +5 -5
- data/lib/search_flip/config.rb +2 -1
- data/lib/search_flip/criteria.rb +83 -294
- data/lib/search_flip/customable.rb +34 -0
- data/lib/search_flip/explainable.rb +28 -0
- data/lib/search_flip/highlightable.rb +49 -0
- data/lib/search_flip/http_client.rb +6 -2
- data/lib/search_flip/index.rb +1 -1
- data/lib/search_flip/null_instrumenter.rb +21 -0
- data/lib/search_flip/paginatable.rb +93 -0
- data/lib/search_flip/response.rb +1 -1
- data/lib/search_flip/sortable.rb +69 -0
- data/lib/search_flip/sourceable.rb +30 -0
- data/lib/search_flip/version.rb +1 -1
- data/search_flip.gemspec +2 -1
- data/spec/search_flip/aggregation_spec.rb +178 -24
- data/spec/search_flip/criteria_spec.rb +42 -0
- data/spec/search_flip/null_instrumenter_spec.rb +43 -0
- metadata +28 -5
@@ -0,0 +1,34 @@
|
|
1
|
+
module SearchFlip
|
2
|
+
# The SearchFlip::Sortable mixin provides the chainable #custom method to
|
3
|
+
# add arbitrary sections to the elasticsearch request
|
4
|
+
|
5
|
+
module Customable
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
attr_accessor :custom_value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Adds a fully custom field/section to the request, such that upcoming or
|
13
|
+
# minor Elasticsearch features as well as other custom requirements can be
|
14
|
+
# used without having yet specialized criteria methods.
|
15
|
+
#
|
16
|
+
# @note Use with caution, because using #custom will potentiall override
|
17
|
+
# other sections like +aggregations+, +query+, +sort+, etc if you use the
|
18
|
+
# the same section names.
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# CommentIndex.custom(section: { argument: "value" }).request
|
22
|
+
# => {:section=>{:argument=>"value"},...}
|
23
|
+
#
|
24
|
+
# @param hash [Hash] The custom section that is added to the request
|
25
|
+
#
|
26
|
+
# @return [SearchFlip::Criteria] A newly created extended criteria
|
27
|
+
|
28
|
+
def custom(hash)
|
29
|
+
fresh.tap do |criteria|
|
30
|
+
criteria.custom_value = (custom_value || {}).merge(hash)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module SearchFlip
|
2
|
+
# The SearchFlip::Sortable mixin provides the chainable #explain method to
|
3
|
+
# control elasticsearch query explanations
|
4
|
+
|
5
|
+
module Explainable
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
attr_accessor :explain_value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Specifies whether or not to enable explanation for each hit on how
|
13
|
+
# its score was computed.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# CommentIndex.explain(true)
|
17
|
+
#
|
18
|
+
# @param value [Boolean] The value for explain
|
19
|
+
#
|
20
|
+
# @return [SearchFlip::Criteria] A newly created extended criteria
|
21
|
+
|
22
|
+
def explain(value)
|
23
|
+
fresh.tap do |criteria|
|
24
|
+
criteria.explain_value = value
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module SearchFlip
|
2
|
+
# The SearchFlip::Sortable mixin provides the chainable #highlight method to
|
3
|
+
# use elasticsearch highlighting
|
4
|
+
|
5
|
+
module Highlightable
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
attr_accessor :highlight_values
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Adds highlighting of the given fields to the request.
|
13
|
+
#
|
14
|
+
# @example
|
15
|
+
# CommentIndex.highlight([:title, :message])
|
16
|
+
# CommentIndex.highlight(:title).highlight(:description)
|
17
|
+
# CommentIndex.highlight(:title, require_field_match: false)
|
18
|
+
# CommentIndex.highlight(title: { type: "fvh" })
|
19
|
+
#
|
20
|
+
# @example
|
21
|
+
# query = CommentIndex.highlight(:title).search("hello")
|
22
|
+
# query.results[0].highlight.title # => "<em>hello</em> world"
|
23
|
+
#
|
24
|
+
# @param fields [Hash, Array, String, Symbol] The fields to highligt.
|
25
|
+
# Supports raw Elasticsearch values by passing a Hash.
|
26
|
+
#
|
27
|
+
# @param options [Hash] Extra highlighting options. Check out the Elasticsearch
|
28
|
+
# docs for further details.
|
29
|
+
#
|
30
|
+
# @return [SearchFlip::Criteria] A new criteria including the highlighting
|
31
|
+
|
32
|
+
def highlight(fields, options = {})
|
33
|
+
fresh.tap do |criteria|
|
34
|
+
criteria.highlight_values = (criteria.highlight_values || {}).merge(options)
|
35
|
+
|
36
|
+
hash =
|
37
|
+
if fields.is_a?(Hash)
|
38
|
+
fields
|
39
|
+
elsif fields.is_a?(Array)
|
40
|
+
fields.each_with_object({}) { |field, h| h[field] = {} }
|
41
|
+
else
|
42
|
+
{ fields => {} }
|
43
|
+
end
|
44
|
+
|
45
|
+
criteria.highlight_values[:fields] = (criteria.highlight_values[:fields] || {}).merge(hash)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -25,17 +25,21 @@ module SearchFlip
|
|
25
25
|
client.request = request.send(method, *args)
|
26
26
|
end
|
27
27
|
end
|
28
|
+
|
29
|
+
ruby2_keywords method
|
28
30
|
end
|
29
31
|
|
30
32
|
[:get, :post, :put, :delete, :head].each do |method|
|
31
|
-
define_method
|
33
|
+
define_method(method) do |*args|
|
32
34
|
execute(method, *args)
|
33
35
|
end
|
36
|
+
|
37
|
+
ruby2_keywords method
|
34
38
|
end
|
35
39
|
|
36
40
|
private
|
37
41
|
|
38
|
-
def execute(method, *args)
|
42
|
+
ruby2_keywords def execute(method, *args)
|
39
43
|
response = request.send(method, *args)
|
40
44
|
|
41
45
|
raise SearchFlip::ResponseError.new(code: response.code, body: response.body.to_s) unless response.status.success?
|
data/lib/search_flip/index.rb
CHANGED
@@ -0,0 +1,21 @@
|
|
1
|
+
module SearchFlip
|
2
|
+
class NullInstrumenter
|
3
|
+
def instrument(name, payload = {})
|
4
|
+
start(name, payload)
|
5
|
+
|
6
|
+
begin
|
7
|
+
yield(payload) if block_given?
|
8
|
+
ensure
|
9
|
+
finish(name, payload)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def start(_name, _payload)
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
def finish(_name, _payload)
|
18
|
+
true
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,93 @@
|
|
1
|
+
module SearchFlip
|
2
|
+
# The SearchFlip::Paginatable mixin provides chainable methods to allow
|
3
|
+
# paginating the search results
|
4
|
+
|
5
|
+
module Paginatable
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
attr_accessor :offset_value, :limit_value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Sets the request offset, ie SearchFlip's from parameter that is used
|
13
|
+
# to skip results in the result set from being returned.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# CommentIndex.offset(100)
|
17
|
+
#
|
18
|
+
# @param value [Fixnum] The offset value, ie the number of results that are
|
19
|
+
# skipped in the result set
|
20
|
+
#
|
21
|
+
# @return [SearchFlip::Criteria] A newly created extended criteria
|
22
|
+
|
23
|
+
def offset(value)
|
24
|
+
fresh.tap do |criteria|
|
25
|
+
criteria.offset_value = value.to_i
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# @api private
|
30
|
+
#
|
31
|
+
# Returns the offset value or, if not yet set, the default limit value (0).
|
32
|
+
#
|
33
|
+
# @return [Fixnum] The offset value
|
34
|
+
|
35
|
+
def offset_value_with_default
|
36
|
+
(offset_value || 0).to_i
|
37
|
+
end
|
38
|
+
|
39
|
+
# Sets the request limit, ie Elasticsearch's size parameter that is used
|
40
|
+
# to restrict the results that get returned.
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# CommentIndex.limit(100)
|
44
|
+
#
|
45
|
+
# @param value [Fixnum] The limit value, ie the max number of results that
|
46
|
+
# should be returned
|
47
|
+
#
|
48
|
+
# @return [SearchFlip::Criteria] A newly created extended criteria
|
49
|
+
|
50
|
+
def limit(value)
|
51
|
+
fresh.tap do |criteria|
|
52
|
+
criteria.limit_value = value.to_i
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
# @api private
|
57
|
+
#
|
58
|
+
# Returns the limit value or, if not yet set, the default limit value (30).
|
59
|
+
#
|
60
|
+
# @return [Fixnum] The limit value
|
61
|
+
|
62
|
+
def limit_value_with_default
|
63
|
+
(limit_value || 30).to_i
|
64
|
+
end
|
65
|
+
|
66
|
+
# Sets pagination parameters for the criteria by using offset and limit,
|
67
|
+
# ie Elasticsearch's from and size parameters.
|
68
|
+
#
|
69
|
+
# @example
|
70
|
+
# CommentIndex.paginate(page: 3)
|
71
|
+
# CommentIndex.paginate(page: 5, per_page: 60)
|
72
|
+
#
|
73
|
+
# @param page [#to_i] The current page
|
74
|
+
# @param per_page [#to_i] The number of results per page
|
75
|
+
#
|
76
|
+
# @return [SearchFlip::Criteria] A newly created extended criteria
|
77
|
+
|
78
|
+
def paginate(page: 1, per_page: 30)
|
79
|
+
page = [page.to_i, 1].max
|
80
|
+
per_page = per_page.to_i
|
81
|
+
|
82
|
+
offset((page - 1) * per_page).limit(per_page)
|
83
|
+
end
|
84
|
+
|
85
|
+
def page(value)
|
86
|
+
paginate(page: value, per_page: limit_value_with_default)
|
87
|
+
end
|
88
|
+
|
89
|
+
def per(value)
|
90
|
+
paginate(page: 1 + (offset_value_with_default / limit_value_with_default), per_page: value)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
data/lib/search_flip/response.rb
CHANGED
@@ -222,7 +222,7 @@ module SearchFlip
|
|
222
222
|
#
|
223
223
|
# @return [Array] An array of database records
|
224
224
|
|
225
|
-
def records
|
225
|
+
def records
|
226
226
|
@records ||= begin
|
227
227
|
sort_map = ids.each_with_index.each_with_object({}) { |(id, index), hash| hash[id.to_s] = index }
|
228
228
|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module SearchFlip
|
2
|
+
# The SearchFlip::Sortable mixin provides the chainable methods #sort as
|
3
|
+
# well as #resort
|
4
|
+
|
5
|
+
module Sortable
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
attr_accessor :sort_values
|
9
|
+
|
10
|
+
alias_method :order, :sort
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
# Specify the sort order you want Elasticsearch to use for sorting the
|
15
|
+
# results. When you call this multiple times, the sort orders are appended
|
16
|
+
# to the already existing ones. The sort arguments get passed to
|
17
|
+
# Elasticsearch without modifications, such that you can use sort by
|
18
|
+
# script, etc here as well.
|
19
|
+
#
|
20
|
+
# @example Default usage
|
21
|
+
# CommentIndex.sort(:user_id, :id)
|
22
|
+
#
|
23
|
+
# # Same as
|
24
|
+
#
|
25
|
+
# CommentIndex.sort(:user_id).sort(:id)
|
26
|
+
#
|
27
|
+
# @example Default hash usage
|
28
|
+
# CommentIndex.sort(user_id: "asc").sort(id: "desc")
|
29
|
+
#
|
30
|
+
# # Same as
|
31
|
+
#
|
32
|
+
# CommentIndex.sort({ user_id: "asc" }, { id: "desc" })
|
33
|
+
#
|
34
|
+
# @example Sort by native script
|
35
|
+
# CommentIndex.sort("_script" => "sort_script", lang: "native", order: "asc", type: "number")
|
36
|
+
#
|
37
|
+
# @param args The sort values that get passed to Elasticsearch
|
38
|
+
#
|
39
|
+
# @return [SearchFlip::Criteria] A newly created extended criteria
|
40
|
+
|
41
|
+
def sort(*args)
|
42
|
+
fresh.tap do |criteria|
|
43
|
+
criteria.sort_values = (sort_values || []) + args
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Specify the sort order you want Elasticsearch to use for sorting the
|
48
|
+
# results with already existing sort orders being removed.
|
49
|
+
#
|
50
|
+
# @example
|
51
|
+
# CommentIndex.sort(user_id: "asc").resort(id: "desc")
|
52
|
+
#
|
53
|
+
# # Same as
|
54
|
+
#
|
55
|
+
# CommentIndex.sort(id: "desc")
|
56
|
+
#
|
57
|
+
# @return [SearchFlip::Criteria] A newly created extended criteria
|
58
|
+
#
|
59
|
+
# @see #sort See #sort for more details
|
60
|
+
|
61
|
+
def resort(*args)
|
62
|
+
fresh.tap do |criteria|
|
63
|
+
criteria.sort_values = args
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
alias_method :reorder, :resort
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module SearchFlip
|
2
|
+
# The SearchFlip::Sortable mixin provides the chainable #source method to
|
3
|
+
# use elasticsearch source filtering
|
4
|
+
|
5
|
+
module Sourceable
|
6
|
+
def self.included(base)
|
7
|
+
base.class_eval do
|
8
|
+
attr_accessor :source_value
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# Use to specify which fields of the source document you want Elasticsearch
|
13
|
+
# to return for each matching result.
|
14
|
+
#
|
15
|
+
# @example
|
16
|
+
# CommentIndex.source([:id, :message]).search("hello world")
|
17
|
+
# CommentIndex.source(exclude: "description")
|
18
|
+
# CommentIndex.source(false)
|
19
|
+
#
|
20
|
+
# @param value Pass any allowed value to restrict the returned source
|
21
|
+
#
|
22
|
+
# @return [SearchFlip::Criteria] A newly created extended criteria
|
23
|
+
|
24
|
+
def source(value)
|
25
|
+
fresh.tap do |criteria|
|
26
|
+
criteria.source_value = value
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/search_flip/version.rb
CHANGED
data/search_flip.gemspec
CHANGED
@@ -20,7 +20,7 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.post_install_message = <<~MESSAGE
|
22
22
|
Thanks for using search_flip!
|
23
|
-
When upgrading
|
23
|
+
When upgrading to 3.x, please check out
|
24
24
|
https://github.com/mrkamel/search_flip/blob/master/UPDATING.md
|
25
25
|
MESSAGE
|
26
26
|
|
@@ -37,4 +37,5 @@ Gem::Specification.new do |spec|
|
|
37
37
|
spec.add_dependency "hashie"
|
38
38
|
spec.add_dependency "http"
|
39
39
|
spec.add_dependency "oj"
|
40
|
+
spec.add_dependency "ruby2_keywords"
|
40
41
|
end
|
@@ -271,8 +271,8 @@ RSpec.describe SearchFlip::Aggregation do
|
|
271
271
|
|
272
272
|
ProductIndex.import [product1, product2, product3, product4]
|
273
273
|
|
274
|
-
query = ProductIndex.aggregate(categories: {}) do |
|
275
|
-
|
274
|
+
query = ProductIndex.aggregate(categories: {}) do |aggregation|
|
275
|
+
aggregation.merge(ProductIndex.where(price: 100..200)).aggregate(:category)
|
276
276
|
end
|
277
277
|
|
278
278
|
result = query.aggregations(:categories).category.buckets.each_with_object({}) do |bucket, hash|
|
@@ -282,33 +282,24 @@ RSpec.describe SearchFlip::Aggregation do
|
|
282
282
|
expect(result).to eq("category1" => 2, "category2" => 1)
|
283
283
|
end
|
284
284
|
|
285
|
-
describe "
|
286
|
-
|
287
|
-
:profile_value, :failsafe_value, :terminate_after_value, :timeout_value, :offset_value, :limit_value,
|
288
|
-
:scroll_args, :highlight_values, :suggest_values, :custom_value, :source_value, :sort_values,
|
289
|
-
:includes_values, :preload_values, :eager_load_values, :post_must_values,
|
290
|
-
:post_must_not_values, :post_filter_values, :preference_value,
|
291
|
-
:search_type_value, :routing_value
|
292
|
-
]
|
285
|
+
describe "assignments" do
|
286
|
+
methods = [:offset_value, :limit_value, :source_value, :explain_value]
|
293
287
|
|
294
|
-
|
295
|
-
it "
|
296
|
-
|
297
|
-
|
298
|
-
criteria = SearchFlip::Criteria.new(target: TestIndex)
|
299
|
-
criteria.send("#{unsupported_method}=", "value")
|
288
|
+
methods.each do |method|
|
289
|
+
it "replaces the values" do
|
290
|
+
aggregation = SearchFlip::Aggregation.new(target: TestIndex)
|
291
|
+
aggregation.send("#{method}=", "value1")
|
300
292
|
|
301
|
-
|
302
|
-
|
303
|
-
end
|
293
|
+
criteria = SearchFlip::Criteria.new(target: TestIndex)
|
294
|
+
criteria.send("#{method}=", "value2")
|
304
295
|
|
305
|
-
expect(
|
296
|
+
expect(aggregation.merge(criteria).send(method)).to eq("value2")
|
306
297
|
end
|
307
298
|
end
|
308
299
|
end
|
309
300
|
|
310
301
|
describe "array concatenations" do
|
311
|
-
methods = [:must_values, :must_not_values, :filter_values]
|
302
|
+
methods = [:sort_values, :must_values, :must_not_values, :filter_values]
|
312
303
|
|
313
304
|
methods.each do |method|
|
314
305
|
it "concatenates the values for #{method}" do
|
@@ -326,7 +317,7 @@ RSpec.describe SearchFlip::Aggregation do
|
|
326
317
|
end
|
327
318
|
|
328
319
|
describe "hash merges" do
|
329
|
-
methods = [:aggregation_values]
|
320
|
+
methods = [:highlight_values, :custom_value, :aggregation_values]
|
330
321
|
|
331
322
|
methods.each do |method|
|
332
323
|
it "merges the values for #{method}" do
|
@@ -342,6 +333,29 @@ RSpec.describe SearchFlip::Aggregation do
|
|
342
333
|
end
|
343
334
|
end
|
344
335
|
end
|
336
|
+
|
337
|
+
describe "unsupported methods" do
|
338
|
+
unsupported_methods = [
|
339
|
+
:profile_value, :failsafe_value, :terminate_after_value, :timeout_value, :scroll_args,
|
340
|
+
:suggest_values, :includes_values, :preload_values, :eager_load_values, :post_must_values,
|
341
|
+
:post_must_not_values, :post_filter_values, :preference_value, :search_type_value, :routing_value
|
342
|
+
]
|
343
|
+
|
344
|
+
unsupported_methods.each do |unsupported_method|
|
345
|
+
it "raises a NotSupportedError #{unsupported_method}" do
|
346
|
+
block = lambda do
|
347
|
+
aggregation = SearchFlip::Aggregation.new(target: TestIndex)
|
348
|
+
|
349
|
+
criteria = SearchFlip::Criteria.new(target: TestIndex)
|
350
|
+
criteria.send("#{unsupported_method}=", "value")
|
351
|
+
|
352
|
+
aggregation.merge(criteria)
|
353
|
+
end
|
354
|
+
|
355
|
+
expect(&block).to raise_error(SearchFlip::NotSupportedError)
|
356
|
+
end
|
357
|
+
end
|
358
|
+
end
|
345
359
|
end
|
346
360
|
|
347
361
|
describe "#respond_to?" do
|
@@ -369,8 +383,8 @@ RSpec.describe SearchFlip::Aggregation do
|
|
369
383
|
|
370
384
|
temp_index.import [product1, product2, product3, product4]
|
371
385
|
|
372
|
-
query = temp_index.aggregate(categories: {}) do |
|
373
|
-
|
386
|
+
query = temp_index.aggregate(categories: {}) do |aggregation|
|
387
|
+
aggregation.merge(temp_index.with_price_range(100..200)).aggregate(:category)
|
374
388
|
end
|
375
389
|
|
376
390
|
result = query.aggregations(:categories).category.buckets.each_with_object({}) do |bucket, hash|
|
@@ -380,4 +394,144 @@ RSpec.describe SearchFlip::Aggregation do
|
|
380
394
|
expect(result).to eq("category1" => 2, "category2" => 1)
|
381
395
|
end
|
382
396
|
end
|
397
|
+
|
398
|
+
describe "#explain" do
|
399
|
+
it "returns the explaination" do
|
400
|
+
ProductIndex.import create(:product)
|
401
|
+
|
402
|
+
query = ProductIndex.aggregate(top_hits: { top_hits: {} }) do |aggregation|
|
403
|
+
aggregation.explain(true)
|
404
|
+
end
|
405
|
+
|
406
|
+
expect(query.aggregations("top_hits").hits.hits.first.key?("_explanation")).to eq(true)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
|
410
|
+
describe "#custom" do
|
411
|
+
it "adds a custom entry to the request" do
|
412
|
+
query = ProductIndex.aggregate(top_hits: { top_hits: {} }) do |aggregation|
|
413
|
+
aggregation.custom(custom_key: "custom_value")
|
414
|
+
end
|
415
|
+
|
416
|
+
expect(query.request[:aggregations][:top_hits][:top_hits][:custom_key]).to eq("custom_value")
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
describe "#highlight" do
|
421
|
+
it "adds a custom entry to the request" do
|
422
|
+
ProductIndex.import create(:product, title: "Title highlight")
|
423
|
+
|
424
|
+
query = ProductIndex.search("title:highlight").aggregate(top_hits: { top_hits: {} }) do |aggregation|
|
425
|
+
aggregation.highlight([:title])
|
426
|
+
end
|
427
|
+
|
428
|
+
expect(query.aggregations("top_hits").hits.hits.first.highlight.title).to be_present
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
describe "#page" do
|
433
|
+
it "returns the respective result window" do
|
434
|
+
product1, product2 = create_list(:product, 2)
|
435
|
+
|
436
|
+
ProductIndex.import [product1, product2]
|
437
|
+
|
438
|
+
query = ProductIndex.aggregate(top_hits: { top_hits: {} }) do |aggregation|
|
439
|
+
aggregation.sort(:id).per(1).page(2)
|
440
|
+
end
|
441
|
+
|
442
|
+
expect(query.aggregations("top_hits").hits.hits.first._id).to eq(product2.id.to_s)
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
describe "#per" do
|
447
|
+
it "returns the respective result window" do
|
448
|
+
ProductIndex.import create_list(:product, 2)
|
449
|
+
|
450
|
+
query = ProductIndex.aggregate(top_hits: { top_hits: {} }) do |aggregation|
|
451
|
+
aggregation.per(1)
|
452
|
+
end
|
453
|
+
|
454
|
+
expect(query.aggregations("top_hits").hits.hits.size).to eq(1)
|
455
|
+
end
|
456
|
+
end
|
457
|
+
|
458
|
+
describe "#paginate" do
|
459
|
+
it "returns the respective result window" do
|
460
|
+
product1, product2 = create_list(:product, 2)
|
461
|
+
|
462
|
+
ProductIndex.import [product1, product2]
|
463
|
+
|
464
|
+
query = ProductIndex.aggregate(top_hits: { top_hits: {} }) do |aggregation|
|
465
|
+
aggregation.sort(:id).paginate(page: 2, per_page: 1)
|
466
|
+
end
|
467
|
+
|
468
|
+
expect(query.aggregations("top_hits").hits.hits.first._id).to eq(product2.id.to_s)
|
469
|
+
end
|
470
|
+
end
|
471
|
+
|
472
|
+
describe "#limit" do
|
473
|
+
it "returns the respective result window" do
|
474
|
+
ProductIndex.import create_list(:product, 2)
|
475
|
+
|
476
|
+
query = ProductIndex.aggregate(top_hits: { top_hits: {} }) do |aggregation|
|
477
|
+
aggregation.limit(1)
|
478
|
+
end
|
479
|
+
|
480
|
+
expect(query.aggregations("top_hits").hits.hits.size).to eq(1)
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
describe "#offset" do
|
485
|
+
it "returns the respective result window" do
|
486
|
+
product1, product2 = create_list(:product, 2)
|
487
|
+
|
488
|
+
ProductIndex.import [product1, product2]
|
489
|
+
|
490
|
+
query = ProductIndex.aggregate(top_hits: { top_hits: {} }) do |aggregation|
|
491
|
+
aggregation.sort(:id).limit(1).offset(1)
|
492
|
+
end
|
493
|
+
|
494
|
+
expect(query.aggregations("top_hits").hits.hits.first._id).to eq(product2.id.to_s)
|
495
|
+
end
|
496
|
+
end
|
497
|
+
|
498
|
+
describe "#sort" do
|
499
|
+
it "returns the results in the specified order" do
|
500
|
+
product1, product2 = create_list(:product, 2)
|
501
|
+
|
502
|
+
ProductIndex.import [product1, product2]
|
503
|
+
|
504
|
+
query = ProductIndex.aggregate(top_hits: { top_hits: {} }) do |aggregation|
|
505
|
+
aggregation.sort(id: "desc")
|
506
|
+
end
|
507
|
+
|
508
|
+
expect(query.aggregations("top_hits").hits.hits.map(&:_id)).to eq([product2, product1].map(&:id).map(&:to_s))
|
509
|
+
end
|
510
|
+
end
|
511
|
+
|
512
|
+
describe "#resort" do
|
513
|
+
it "overrides the previous sorting and returns the results in the specified order" do
|
514
|
+
product1, product2 = create_list(:product, 2)
|
515
|
+
|
516
|
+
ProductIndex.import [product1, product2]
|
517
|
+
|
518
|
+
query = ProductIndex.aggregate(top_hits: { top_hits: {} }) do |aggregation|
|
519
|
+
aggregation.sort(id: "desc").resort(:id)
|
520
|
+
end
|
521
|
+
|
522
|
+
expect(query.aggregations("top_hits").hits.hits.map(&:_id)).to eq([product1, product2].map(&:id).map(&:to_s))
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
describe "#source" do
|
527
|
+
it "returns the specified fields only" do
|
528
|
+
ProductIndex.import create(:product)
|
529
|
+
|
530
|
+
query = ProductIndex.aggregate(top_hits: { top_hits: {} }) do |aggregation|
|
531
|
+
aggregation.source([:id, :title])
|
532
|
+
end
|
533
|
+
|
534
|
+
expect(query.aggregations("top_hits").hits.hits.first._source.keys).to eq(["id", "title"])
|
535
|
+
end
|
536
|
+
end
|
383
537
|
end
|