search_flip 3.0.0.beta2 → 3.0.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8594c0c22592c2dfa8fb54097151a87db3746221586b8c28e4c6c3c5c02dff6c
4
- data.tar.gz: 55b27e4f8e3bc04c4420280d1dccafbf66e5f7e53cf6914c16f5e5dd3cb5ae10
3
+ metadata.gz: 417bf711946ace4812485ea2efdfefdeb36414e7e8e8d8ed24b10b4807050be9
4
+ data.tar.gz: c8a9f7392d77f15c4714c40756c032a2bb3bc12c363a9e956a6b9e184c8e4c17
5
5
  SHA512:
6
- metadata.gz: d54c4c22ba4637573d590288cf1a2407d0ff066e9c4505e3ee851a431ff6ebdc318424ea3af63b2c4d426f74521e27a1800c361feb97dec37c7d1159328dc819
7
- data.tar.gz: 1b1bd5e46c5edfc2f1c7d3674564b1bb5627956aee05e16d06e1bdbf1107a70cdd1114bc530cb380db40cc16f383481bafc573c63e1ac3e26b218df97f0d65ed
6
+ metadata.gz: c62c6d3bc17ec18da5b304dfbddbafc29e35013bcc6b0b906858380b01a263b2e534986cd41f9e90d13de99820f9cbf11a173802910737bf33d8ae5653230af1
7
+ data.tar.gz: 3b6ee6d16086e451f6f6e6d7c718d6a30c30115d91c6ba9e69113faa19b9abc35d04c8c179dc9ca5ac49418877c0c5828435d71c1dab7b71d4afe2e31da88762
data/CHANGELOG.md CHANGED
@@ -21,6 +21,8 @@
21
21
  * `Connection#freeze_index`, `Connection#unfreeze_index`, `Index#freeze_index`
22
22
  and `Index#unfreeze_index` added
23
23
  * Added `SearchFlip::Result.from_hit`
24
+ * Added support for `source`, `sort`, `page`, `per`, `paginate`, `explain`, and
25
+ `highlight` to aggregations
24
26
 
25
27
  ## v2.3.1
26
28
 
data/README.md CHANGED
@@ -505,6 +505,14 @@ end
505
505
  query.aggregations(:average_price).average_price.value
506
506
  ```
507
507
 
508
+ Even various criteria for top hits aggregations can be specified elegantly:
509
+
510
+ ```ruby
511
+ query = ProductIndex.aggregate(sponsored: { top_hits: {} }) do |aggregation|
512
+ aggregation.sort(:rank).highlight(:title).source([:id, :title])
513
+ end
514
+ ```
515
+
508
516
  Checkout [Aggregatable](http://www.rubydoc.info/github/mrkamel/search_flip/SearchFlip/Aggregatable)
509
517
  as well as [Aggregation](http://www.rubydoc.info/github/mrkamel/search_flip/SearchFlip/Aggregation)
510
518
  for a complete API reference.
@@ -57,7 +57,14 @@ module SearchFlip
57
57
  if block
58
58
  aggregation = yield(SearchFlip::Aggregation.new(target: target))
59
59
 
60
- field_or_hash.is_a?(Hash) ? hash[field_or_hash.keys.first].merge!(aggregation.to_hash) : hash[field_or_hash].merge!(aggregation.to_hash)
60
+ if field_or_hash.is_a?(Hash)
61
+ value = field_or_hash.values.first
62
+ value = value.values.first if value.is_a?(Hash) && !value.empty?
63
+
64
+ value.merge!(aggregation.to_hash)
65
+ else
66
+ hash[field_or_hash].merge!(aggregation.to_hash)
67
+ end
61
68
  end
62
69
 
63
70
  criteria.aggregation_values = (aggregation_values || {}).merge(hash)
@@ -6,6 +6,12 @@ module SearchFlip
6
6
  class Aggregation
7
7
  include Filterable
8
8
  include Aggregatable
9
+ include Paginatable
10
+ include Highlightable
11
+ include Explainable
12
+ include Sourceable
13
+ include Sortable
14
+ include Customable
9
15
 
10
16
  attr_reader :target
11
17
 
@@ -40,6 +46,15 @@ module SearchFlip
40
46
  end
41
47
  end
42
48
 
49
+ res.update(from: offset_value_with_default, size: limit_value_with_default) if offset_value || limit_value
50
+
51
+ res[:explain] = explain_value unless explain_value.nil?
52
+ res[:highlight] = highlight_values if highlight_values
53
+ res[:sort] = sort_values if sort_values
54
+ res[:_source] = source_value unless source_value.nil?
55
+
56
+ res.update(custom_value) if custom_value
57
+
43
58
  res
44
59
  end
45
60
 
@@ -56,11 +71,9 @@ module SearchFlip
56
71
 
57
72
  fresh.tap do |aggregation|
58
73
  unsupported_methods = [
59
- :profile_value, :failsafe_value, :terminate_after_value, :timeout_value, :offset_value, :limit_value,
60
- :scroll_args, :highlight_values, :suggest_values, :custom_value, :source_value, :sort_values,
61
- :includes_values, :preload_values, :eager_load_values, :post_must_values,
62
- :post_must_not_values, :post_filter_values, :preference_value,
63
- :search_type_value, :routing_value
74
+ :profile_value, :failsafe_value, :terminate_after_value, :timeout_value, :scroll_args,
75
+ :suggest_values, :includes_values, :preload_values, :eager_load_values, :post_must_values,
76
+ :post_must_not_values, :post_filter_values, :preference_value, :search_type_value, :routing_value
64
77
  ]
65
78
 
66
79
  unsupported_methods.each do |unsupported_method|
@@ -69,10 +82,19 @@ module SearchFlip
69
82
  end
70
83
  end
71
84
 
85
+ aggregation.source_value = other.source_value if other.source_value
86
+ aggregation.offset_value = other.offset_value if other.offset_value
87
+ aggregation.limit_value = other.limit_value if other.limit_value
88
+ aggregation.scroll_args = other.scroll_args if other.scroll_args
89
+ aggregation.explain_value = other.explain_value unless other.explain_value.nil?
90
+
91
+ aggregation.sort_values = (aggregation.sort_values || []) + other.sort_values if other.sort_values
72
92
  aggregation.must_values = (aggregation.must_values || []) + other.must_values if other.must_values
73
93
  aggregation.must_not_values = (aggregation.must_not_values || []) + other.must_not_values if other.must_not_values
74
94
  aggregation.filter_values = (aggregation.filter_values || []) + other.filter_values if other.filter_values
75
95
 
96
+ aggregation.highlight_values = (aggregation.highlight_values || {}).merge(other.highlight_values) if other.highlight_values
97
+ aggregation.custom_value = (aggregation.custom_value || {}).merge(other.custom_value) if other.custom_value
76
98
  aggregation.aggregation_values = (aggregation.aggregation_values || {}).merge(other.aggregation_values) if other.aggregation_values
77
99
  end
78
100
  end
@@ -13,15 +13,20 @@ module SearchFlip
13
13
  # CommentIndex.sort("_doc").find_each { |comment| "..." }
14
14
 
15
15
  class Criteria
16
+ include Sortable
17
+ include Sourceable
18
+ include Highlightable
19
+ include Explainable
20
+ include Paginatable
21
+ include Customable
16
22
  include Filterable
17
23
  include PostFilterable
18
24
  include Aggregatable
19
25
  extend Forwardable
20
26
 
21
- attr_accessor :target, :profile_value, :source_value, :sort_values, :highlight_values, :suggest_values,
22
- :offset_value, :limit_value, :includes_values, :eager_load_values, :preload_values, :failsafe_value,
23
- :scroll_args, :custom_value, :terminate_after_value, :timeout_value, :preference_value, :search_type_value,
24
- :routing_value, :track_total_hits_value, :explain_value
27
+ attr_accessor :target, :profile_value, :source_value, :suggest_values, :includes_values,
28
+ :eager_load_values, :preload_values, :failsafe_value, :scroll_args, :terminate_after_value,
29
+ :timeout_value, :preference_value, :search_type_value, :routing_value, :track_total_hits_value
25
30
 
26
31
  # Creates a new criteria while merging the attributes (constraints,
27
32
  # settings, etc) of the current criteria with the attributes of another one
@@ -71,22 +76,6 @@ module SearchFlip
71
76
  end
72
77
  end
73
78
 
74
- # Specifies whether or not to enable explanation for each hit on how
75
- # its score was computed.
76
- #
77
- # @example
78
- # CommentIndex.explain(true)
79
- #
80
- # @param value [Boolean] The value for explain
81
- #
82
- # @return [SearchFlip::Criteria] A newly created extended criteria
83
-
84
- def explain(value)
85
- fresh.tap do |criteria|
86
- criteria.explain_value = value
87
- end
88
- end
89
-
90
79
  # Specifies if or how many hits should be counted/tracked. Check out the
91
80
  # elasticsearch docs for futher details.
92
81
  #
@@ -271,43 +260,6 @@ module SearchFlip
271
260
  res
272
261
  end
273
262
 
274
- # Adds highlighting of the given fields to the request.
275
- #
276
- # @example
277
- # CommentIndex.highlight([:title, :message])
278
- # CommentIndex.highlight(:title).highlight(:description)
279
- # CommentIndex.highlight(:title, require_field_match: false)
280
- # CommentIndex.highlight(title: { type: "fvh" })
281
- #
282
- # @example
283
- # query = CommentIndex.highlight(:title).search("hello")
284
- # query.results[0].highlight.title # => "<em>hello</em> world"
285
- #
286
- # @param fields [Hash, Array, String, Symbol] The fields to highligt.
287
- # Supports raw Elasticsearch values by passing a Hash.
288
- #
289
- # @param options [Hash] Extra highlighting options. Check out the Elasticsearch
290
- # docs for further details.
291
- #
292
- # @return [SearchFlip::Criteria] A new criteria including the highlighting
293
-
294
- def highlight(fields, options = {})
295
- fresh.tap do |criteria|
296
- criteria.highlight_values = (criteria.highlight_values || {}).merge(options)
297
-
298
- hash =
299
- if fields.is_a?(Hash)
300
- fields
301
- elsif fields.is_a?(Array)
302
- fields.each_with_object({}) { |field, h| h[field] = {} }
303
- else
304
- { fields => {} }
305
- end
306
-
307
- criteria.highlight_values[:fields] = (criteria.highlight_values[:fields] || {}).merge(hash)
308
- end
309
- end
310
-
311
263
  # Adds a suggestion section with the given name to the request.
312
264
  #
313
265
  # @example
@@ -397,24 +349,6 @@ module SearchFlip
397
349
  true
398
350
  end
399
351
 
400
- # Use to specify which fields of the source document you want Elasticsearch
401
- # to return for each matching result.
402
- #
403
- # @example
404
- # CommentIndex.source([:id, :message]).search("hello world")
405
- # CommentIndex.source(exclude: "description")
406
- # CommentIndex.source(false)
407
- #
408
- # @param value Pass any allowed value to restrict the returned source
409
- #
410
- # @return [SearchFlip::Criteria] A newly created extended criteria
411
-
412
- def source(value)
413
- fresh.tap do |criteria|
414
- criteria.source_value = value
415
- end
416
- end
417
-
418
352
  # Specify associations of the target model you want to include via
419
353
  # ActiveRecord's or other ORM's mechanisms when records get fetched from
420
354
  # the database.
@@ -472,166 +406,6 @@ module SearchFlip
472
406
  end
473
407
  end
474
408
 
475
- # Specify the sort order you want Elasticsearch to use for sorting the
476
- # results. When you call this multiple times, the sort orders are appended
477
- # to the already existing ones. The sort arguments get passed to
478
- # Elasticsearch without modifications, such that you can use sort by
479
- # script, etc here as well.
480
- #
481
- # @example Default usage
482
- # CommentIndex.sort(:user_id, :id)
483
- #
484
- # # Same as
485
- #
486
- # CommentIndex.sort(:user_id).sort(:id)
487
- #
488
- # @example Default hash usage
489
- # CommentIndex.sort(user_id: "asc").sort(id: "desc")
490
- #
491
- # # Same as
492
- #
493
- # CommentIndex.sort({ user_id: "asc" }, { id: "desc" })
494
- #
495
- # @example Sort by native script
496
- # CommentIndex.sort("_script" => "sort_script", lang: "native", order: "asc", type: "number")
497
- #
498
- # @param args The sort values that get passed to Elasticsearch
499
- #
500
- # @return [SearchFlip::Criteria] A newly created extended criteria
501
-
502
- def sort(*args)
503
- fresh.tap do |criteria|
504
- criteria.sort_values = (sort_values || []) + args
505
- end
506
- end
507
-
508
- alias_method :order, :sort
509
-
510
- # Specify the sort order you want Elasticsearch to use for sorting the
511
- # results with already existing sort orders being removed.
512
- #
513
- # @example
514
- # CommentIndex.sort(user_id: "asc").resort(id: "desc")
515
- #
516
- # # Same as
517
- #
518
- # CommentIndex.sort(id: "desc")
519
- #
520
- # @return [SearchFlip::Criteria] A newly created extended criteria
521
- #
522
- # @see #sort See #sort for more details
523
-
524
- def resort(*args)
525
- fresh.tap do |criteria|
526
- criteria.sort_values = args
527
- end
528
- end
529
-
530
- alias_method :reorder, :resort
531
-
532
- # Adds a fully custom field/section to the request, such that upcoming or
533
- # minor Elasticsearch features as well as other custom requirements can be
534
- # used without having yet specialized criteria methods.
535
- #
536
- # @note Use with caution, because using #custom will potentiall override
537
- # other sections like +aggregations+, +query+, +sort+, etc if you use the
538
- # the same section names.
539
- #
540
- # @example
541
- # CommentIndex.custom(section: { argument: "value" }).request
542
- # => {:section=>{:argument=>"value"},...}
543
- #
544
- # @param hash [Hash] The custom section that is added to the request
545
- #
546
- # @return [SearchFlip::Criteria] A newly created extended criteria
547
-
548
- def custom(hash)
549
- fresh.tap do |criteria|
550
- criteria.custom_value = (custom_value || {}).merge(hash)
551
- end
552
- end
553
-
554
- # Sets the request offset, ie SearchFlip's from parameter that is used
555
- # to skip results in the result set from being returned.
556
- #
557
- # @example
558
- # CommentIndex.offset(100)
559
- #
560
- # @param value [Fixnum] The offset value, ie the number of results that are
561
- # skipped in the result set
562
- #
563
- # @return [SearchFlip::Criteria] A newly created extended criteria
564
-
565
- def offset(value)
566
- fresh.tap do |criteria|
567
- criteria.offset_value = value.to_i
568
- end
569
- end
570
-
571
- # @api private
572
- #
573
- # Returns the offset value or, if not yet set, the default limit value (0).
574
- #
575
- # @return [Fixnum] The offset value
576
-
577
- def offset_value_with_default
578
- (offset_value || 0).to_i
579
- end
580
-
581
- # Sets the request limit, ie Elasticsearch's size parameter that is used
582
- # to restrict the results that get returned.
583
- #
584
- # @example
585
- # CommentIndex.limit(100)
586
- #
587
- # @param value [Fixnum] The limit value, ie the max number of results that
588
- # should be returned
589
- #
590
- # @return [SearchFlip::Criteria] A newly created extended criteria
591
-
592
- def limit(value)
593
- fresh.tap do |criteria|
594
- criteria.limit_value = value.to_i
595
- end
596
- end
597
-
598
- # @api private
599
- #
600
- # Returns the limit value or, if not yet set, the default limit value (30).
601
- #
602
- # @return [Fixnum] The limit value
603
-
604
- def limit_value_with_default
605
- (limit_value || 30).to_i
606
- end
607
-
608
- # Sets pagination parameters for the criteria by using offset and limit,
609
- # ie Elasticsearch's from and size parameters.
610
- #
611
- # @example
612
- # CommentIndex.paginate(page: 3)
613
- # CommentIndex.paginate(page: 5, per_page: 60)
614
- #
615
- # @param page [#to_i] The current page
616
- # @param per_page [#to_i] The number of results per page
617
- #
618
- # @return [SearchFlip::Criteria] A newly created extended criteria
619
-
620
- def paginate(page: 1, per_page: 30)
621
- page = [page.to_i, 1].max
622
- per_page = per_page.to_i
623
-
624
- offset((page - 1) * per_page).limit(per_page)
625
- end
626
-
627
- def page(value)
628
- paginate(page: value, per_page: limit_value_with_default)
629
- end
630
-
631
- def per(value)
632
- paginate(page: 1 + (offset_value_with_default / limit_value_with_default), per_page: value)
633
- end
634
-
635
409
  # Fetches the records specified by the criteria in batches using the
636
410
  # ElasicSearch scroll API and yields each batch. The batch size and scroll
637
411
  # API timeout can be specified. Check out the Elasticsearch docs for
@@ -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
@@ -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
@@ -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
@@ -1,3 +1,3 @@
1
1
  module SearchFlip
2
- VERSION = "3.0.0.beta2"
2
+ VERSION = "3.0.0.beta3"
3
3
  end
data/lib/search_flip.rb CHANGED
@@ -14,6 +14,12 @@ require "search_flip/config"
14
14
  require "search_flip/connection"
15
15
  require "search_flip/bulk"
16
16
  require "search_flip/filterable"
17
+ require "search_flip/customable"
18
+ require "search_flip/explainable"
19
+ require "search_flip/highlightable"
20
+ require "search_flip/paginatable"
21
+ require "search_flip/sortable"
22
+ require "search_flip/sourceable"
17
23
  require "search_flip/post_filterable"
18
24
  require "search_flip/aggregatable"
19
25
  require "search_flip/aggregation"
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 from 1.x to 2.x, please check out
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
 
@@ -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 |agg|
275
- agg.merge(ProductIndex.where(price: 100..200)).aggregate(:category)
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 "unsupported methods" do
286
- unsupported_methods = [
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
- unsupported_methods.each do |unsupported_method|
295
- it "raises a NotSupportedError #{unsupported_method}" do
296
- block = lambda do
297
- TestIndex.aggregate(field: {}) do |agg|
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
- agg.merge(criteria)
302
- end
303
- end
293
+ criteria = SearchFlip::Criteria.new(target: TestIndex)
294
+ criteria.send("#{method}=", "value2")
304
295
 
305
- expect(&block).to raise_error(SearchFlip::NotSupportedError)
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 |agg|
373
- agg.merge(temp_index.with_price_range(100..200)).aggregate(:category)
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
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: search_flip
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.0.beta2
4
+ version: 3.0.0.beta3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Benjamin Vetter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-04-09 00:00:00.000000000 Z
11
+ date: 2020-04-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -202,16 +202,22 @@ files:
202
202
  - lib/search_flip/config.rb
203
203
  - lib/search_flip/connection.rb
204
204
  - lib/search_flip/criteria.rb
205
+ - lib/search_flip/customable.rb
205
206
  - lib/search_flip/exceptions.rb
207
+ - lib/search_flip/explainable.rb
206
208
  - lib/search_flip/filterable.rb
207
209
  - lib/search_flip/helper.rb
210
+ - lib/search_flip/highlightable.rb
208
211
  - lib/search_flip/http_client.rb
209
212
  - lib/search_flip/index.rb
210
213
  - lib/search_flip/json.rb
211
214
  - lib/search_flip/model.rb
215
+ - lib/search_flip/paginatable.rb
212
216
  - lib/search_flip/post_filterable.rb
213
217
  - lib/search_flip/response.rb
214
218
  - lib/search_flip/result.rb
219
+ - lib/search_flip/sortable.rb
220
+ - lib/search_flip/sourceable.rb
215
221
  - lib/search_flip/to_json.rb
216
222
  - lib/search_flip/version.rb
217
223
  - search_flip.gemspec
@@ -233,7 +239,7 @@ licenses:
233
239
  metadata: {}
234
240
  post_install_message: |
235
241
  Thanks for using search_flip!
236
- When upgrading from 1.x to 2.x, please check out
242
+ When upgrading to 3.x, please check out
237
243
  https://github.com/mrkamel/search_flip/blob/master/UPDATING.md
238
244
  rdoc_options: []
239
245
  require_paths: