caoutsearch 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +816 -7
  3. data/lib/caoutsearch/config/mappings.rb +1 -1
  4. data/lib/caoutsearch/filter/base.rb +11 -7
  5. data/lib/caoutsearch/filter/boolean.rb +1 -1
  6. data/lib/caoutsearch/filter/date.rb +93 -22
  7. data/lib/caoutsearch/filter/default.rb +10 -10
  8. data/lib/caoutsearch/filter/geo_point.rb +1 -1
  9. data/lib/caoutsearch/filter/match.rb +5 -5
  10. data/lib/caoutsearch/filter/none.rb +1 -1
  11. data/lib/caoutsearch/filter/range.rb +6 -6
  12. data/lib/caoutsearch/index/document.rb +11 -11
  13. data/lib/caoutsearch/index/indice_versions.rb +3 -3
  14. data/lib/caoutsearch/index/internal_dsl.rb +3 -3
  15. data/lib/caoutsearch/index/reindex.rb +11 -11
  16. data/lib/caoutsearch/index/scoping.rb +3 -3
  17. data/lib/caoutsearch/index/serialization.rb +13 -13
  18. data/lib/caoutsearch/instrumentation/base.rb +12 -12
  19. data/lib/caoutsearch/instrumentation/search.rb +11 -2
  20. data/lib/caoutsearch/mappings.rb +1 -1
  21. data/lib/caoutsearch/model/indexable.rb +57 -0
  22. data/lib/caoutsearch/model/searchable.rb +31 -0
  23. data/lib/caoutsearch/model.rb +12 -0
  24. data/lib/caoutsearch/response/aggregations.rb +50 -0
  25. data/lib/caoutsearch/response/response.rb +9 -0
  26. data/lib/caoutsearch/response/suggestions.rb +9 -0
  27. data/lib/caoutsearch/response.rb +6 -0
  28. data/lib/caoutsearch/search/adapter/active_record.rb +39 -0
  29. data/lib/caoutsearch/search/base.rb +16 -15
  30. data/lib/caoutsearch/search/batch/scroll.rb +93 -0
  31. data/lib/caoutsearch/search/batch/search_after.rb +70 -0
  32. data/lib/caoutsearch/search/batch_methods.rb +63 -0
  33. data/lib/caoutsearch/search/callbacks.rb +28 -0
  34. data/lib/caoutsearch/search/delete_methods.rb +19 -0
  35. data/lib/caoutsearch/search/dsl/item.rb +2 -2
  36. data/lib/caoutsearch/search/inspect.rb +34 -0
  37. data/lib/caoutsearch/search/instrumentation.rb +19 -0
  38. data/lib/caoutsearch/search/internal_dsl.rb +107 -0
  39. data/lib/caoutsearch/search/naming.rb +45 -0
  40. data/lib/caoutsearch/search/point_in_time.rb +28 -0
  41. data/lib/caoutsearch/search/query/boolean.rb +4 -4
  42. data/lib/caoutsearch/search/query/nested.rb +1 -1
  43. data/lib/caoutsearch/search/query/setters.rb +4 -4
  44. data/lib/caoutsearch/search/query_builder/aggregations.rb +49 -0
  45. data/lib/caoutsearch/search/query_builder.rb +89 -0
  46. data/lib/caoutsearch/search/query_methods.rb +157 -0
  47. data/lib/caoutsearch/search/records.rb +23 -0
  48. data/lib/caoutsearch/search/resettable.rb +38 -0
  49. data/lib/caoutsearch/search/response.rb +97 -0
  50. data/lib/caoutsearch/search/sanitizer.rb +2 -2
  51. data/lib/caoutsearch/search/search_methods.rb +239 -0
  52. data/lib/caoutsearch/search/type_cast.rb +14 -6
  53. data/lib/caoutsearch/search/value.rb +10 -10
  54. data/lib/caoutsearch/search/value_overflow.rb +1 -1
  55. data/lib/caoutsearch/settings.rb +1 -1
  56. data/lib/caoutsearch/testing/mock_requests.rb +105 -0
  57. data/lib/caoutsearch/testing.rb +3 -0
  58. data/lib/caoutsearch/version.rb +1 -1
  59. data/lib/caoutsearch.rb +10 -5
  60. metadata +44 -126
  61. data/lib/caoutsearch/search/search/delete_methods.rb +0 -21
  62. data/lib/caoutsearch/search/search/inspect.rb +0 -36
  63. data/lib/caoutsearch/search/search/instrumentation.rb +0 -21
  64. data/lib/caoutsearch/search/search/internal_dsl.rb +0 -77
  65. data/lib/caoutsearch/search/search/naming.rb +0 -47
  66. data/lib/caoutsearch/search/search/query_builder.rb +0 -94
  67. data/lib/caoutsearch/search/search/query_methods.rb +0 -180
  68. data/lib/caoutsearch/search/search/resettable.rb +0 -35
  69. data/lib/caoutsearch/search/search/response.rb +0 -88
  70. data/lib/caoutsearch/search/search/scroll_methods.rb +0 -113
  71. data/lib/caoutsearch/search/search/search_methods.rb +0 -230
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module InternalDSL
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ # Do to not make:
10
+ # self.config[:filter][key] = ...
11
+ #
12
+ # Changing only config members will lead to inheritance issue.
13
+ # Instead, make:
14
+ # self.config = config.deep_dup
15
+ # self.config[:filter][key] = ...
16
+ #
17
+ class_attribute :config, default: {
18
+ contexts: ActiveSupport::HashWithIndifferentAccess.new,
19
+ filters: ActiveSupport::HashWithIndifferentAccess.new,
20
+ defaults: ActiveSupport::HashWithIndifferentAccess.new,
21
+ suggestions: ActiveSupport::HashWithIndifferentAccess.new,
22
+ sorts: ActiveSupport::HashWithIndifferentAccess.new
23
+ }
24
+
25
+ class_attribute :aggregations, instance_accessor: false, default: {}
26
+ class_attribute :transformations, instance_accessor: false, default: {}
27
+ end
28
+
29
+ class_methods do
30
+ def match_all(&block)
31
+ self.config = config.deep_dup
32
+ config[:match_all] = block
33
+ end
34
+
35
+ %w[context default].each do |method|
36
+ config_attribute = method.pluralize.to_sym
37
+
38
+ define_method method do |name = nil, &block|
39
+ self.config = config.deep_dup
40
+
41
+ if name
42
+ config[config_attribute][name] = Caoutsearch::Search::DSL::Item.new(name, &block)
43
+ else
44
+ config[config_attribute][:__undef__] = block
45
+ end
46
+ end
47
+ end
48
+
49
+ %w[filter sort suggestion].each do |method|
50
+ config_attribute = method.pluralize.to_sym
51
+
52
+ define_method method do |name = nil, **options, &block|
53
+ self.config = config.deep_dup
54
+
55
+ if name
56
+ config[config_attribute][name.to_s] = Caoutsearch::Search::DSL::Item.new(name, options, &block)
57
+ else
58
+ config[config_attribute][:__undef__] = block
59
+ end
60
+ end
61
+ end
62
+
63
+ def alias_filter(new_name, old_name)
64
+ filter(new_name) { |value| search_by(old_name => value) }
65
+ end
66
+
67
+ def alias_sort(new_name, old_name)
68
+ sort(new_name) { |direction| sort_by(old_name, direction) }
69
+ end
70
+
71
+ def has_aggregation(name, **options, &block)
72
+ raise ArgumentError, "has_aggregation accepts options or block but not both" if block && options.any?
73
+
74
+ self.aggregations = aggregations.dup
75
+ aggregations[name.to_s] = Caoutsearch::Search::DSL::Item.new(name, options, &block)
76
+ end
77
+
78
+ def alias_aggregation(new_name, old_name)
79
+ has_aggregation(new_name) do |*args|
80
+ call_aggregation(old_name, *args)
81
+ end
82
+ end
83
+
84
+ def transform_aggregation(name, from: nil, track_total_hits: false, &block)
85
+ name = name.to_s
86
+ dependencies = Array.wrap(from).map(&:to_s)
87
+
88
+ raise ArgumentError, "block is missing" unless block
89
+
90
+ if aggregations.exclude?(name)
91
+ raise ArgumentError, "aggregation #{name} is missing, you may have to define :requires" if dependencies.empty?
92
+
93
+ has_aggregation(name) do |*args|
94
+ track_total_hits! if track_total_hits
95
+ dependencies.each do |dependency|
96
+ call_aggregation(dependency, *args)
97
+ end
98
+ end
99
+ end
100
+
101
+ self.transformations = transformations.dup
102
+ transformations[name.to_s] = Caoutsearch::Search::DSL::Item.new(name, {from: from}, &block)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module Naming
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ delegate :model, :model_name, :index_name, to: :class
10
+ end
11
+
12
+ class_methods do
13
+ def model
14
+ @model ||= model_name.constantize
15
+ end
16
+
17
+ def model_name
18
+ @model_name ||= defaut_model_name
19
+ end
20
+
21
+ def model_name=(name)
22
+ @model_name = name
23
+ end
24
+
25
+ def index_name
26
+ @index_name ||= default_index_name
27
+ end
28
+
29
+ def index_name=(name)
30
+ @index_name = name
31
+ end
32
+
33
+ private
34
+
35
+ def default_index_name
36
+ name.gsub(/Search$/, "").tableize.tr("/", "_")
37
+ end
38
+
39
+ def defaut_model_name
40
+ name.gsub(/Search$/, "")
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module PointInTime
6
+ def open_point_in_time(index: index_name, keep_alive: "1m")
7
+ results = client.open_point_in_time(index: index, keep_alive: keep_alive)
8
+ results["id"]
9
+ end
10
+
11
+ def close_point_in_time(pit_id)
12
+ results = client.close_point_in_time(body: {id: pit_id})
13
+ results.body
14
+ rescue ::Elastic::Transport::Transport::Errors::NotFound
15
+ # We dont care if the PIT ID is already expired
16
+ end
17
+
18
+ def opened_points_in_time
19
+ results = client.indices.stats(index: index_name, metric: "search,shard_stats")
20
+
21
+ open_contexts = results.dig("_all", "total", "search", "open_contexts")
22
+ number_of_shards = results.dig("_all", "primaries", "shard_stats", "total_count")
23
+
24
+ open_contexts / number_of_shards
25
+ end
26
+ end
27
+ end
28
+ end
@@ -5,7 +5,7 @@ module Caoutsearch
5
5
  module Query
6
6
  module Boolean
7
7
  def should_filter_on(terms)
8
- terms = flatten_bool_terms(:should, terms)
8
+ terms = flatten_bool_terms(:should, terms)
9
9
  terms_without_none = terms.without(Caoutsearch::Filter::NONE)
10
10
  return if terms.empty?
11
11
 
@@ -14,7 +14,7 @@ module Caoutsearch
14
14
  elsif terms_without_none.size == 1
15
15
  filters << terms_without_none[0]
16
16
  elsif terms_without_none.size > 1
17
- filters << { bool: { should: terms_without_none } }
17
+ filters << {bool: {should: terms_without_none}}
18
18
  end
19
19
  end
20
20
 
@@ -38,9 +38,9 @@ module Caoutsearch
38
38
 
39
39
  filters <<
40
40
  if terms.size == 1
41
- { bool: { must_not: terms[0] } }
41
+ {bool: {must_not: terms[0]}}
42
42
  else
43
- filters << { bool: { must_not: terms } }
43
+ {bool: {must_not: terms}}
44
44
  end
45
45
  end
46
46
 
@@ -13,7 +13,7 @@ module Caoutsearch
13
13
  query = Caoutsearch::Search::Query::Base.new
14
14
  query[:path] = path.to_s
15
15
 
16
- filters << { nested: query }
16
+ filters << {nested: query}
17
17
  query
18
18
  end
19
19
  end
@@ -30,7 +30,7 @@ module Caoutsearch
30
30
  filter_class = Caoutsearch::Filter[as]
31
31
  raise ArgumentError, "unexpected type of filter: #{as.inspect}" unless filter_class
32
32
 
33
- terms = Array.wrap(keys).flat_map { |key| filter_class.new(key, value, type, options).as_json }
33
+ terms = Array.wrap(keys).flat_map { |key| filter_class.call(key, value, type, options) }
34
34
  terms.select(&:present?)
35
35
  rescue Caoutsearch::Search::ValueOverflow
36
36
  [Caoutsearch::Filter::NONE]
@@ -54,11 +54,11 @@ module Caoutsearch
54
54
  # Suggestions should probably use _source: false
55
55
  def add_suggestion(key, value, **options)
56
56
  suggestions[key] = {
57
- prefix: value,
57
+ prefix: value,
58
58
  completion: options.reverse_merge(
59
- field: key,
59
+ field: key,
60
60
  skip_duplicates: true,
61
- fuzzy: true
61
+ fuzzy: true
62
62
  )
63
63
  }
64
64
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module QueryBuilder
6
+ module Aggregations
7
+ private
8
+
9
+ def build_aggregations
10
+ call_aggregations(*current_aggregations) if current_aggregations
11
+ end
12
+
13
+ def call_aggregations(*args)
14
+ args.each do |arg|
15
+ if arg.is_a?(Hash)
16
+ arg.each do |key, value|
17
+ call_aggregation(key, value)
18
+ end
19
+ else
20
+ call_aggregation(arg)
21
+ end
22
+ end
23
+ end
24
+
25
+ def call_aggregation(name, *args)
26
+ name = name.to_s
27
+
28
+ if self.class.aggregations.include?(name)
29
+ item = self.class.aggregations[name]
30
+ call_aggregation_item(item, *args)
31
+ elsif self.class.aggregations.include?(:__default__)
32
+ block = self.class.aggregations[:__default__]
33
+ instance_exec(name, *args, &block)
34
+ else
35
+ raise "unexpected aggregation: #{name}"
36
+ end
37
+ end
38
+
39
+ def call_aggregation_item(item, *args)
40
+ if item.has_block?
41
+ instance_exec(*args, &item.block)
42
+ else
43
+ query.aggregations[item.name] = item.options
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module QueryBuilder
6
+ extend ActiveSupport::Concern
7
+ include QueryBuilder::Aggregations
8
+
9
+ def build
10
+ reset_variable(:@elasticsearch_query)
11
+ reset_variable(:@nested_queries)
12
+
13
+ run_callbacks :build do
14
+ build_prepend_hash
15
+ build_search_criteria
16
+ build_context
17
+ build_defaults
18
+ build_limits
19
+ build_orders
20
+ build_aggregations
21
+ build_suggestions
22
+ build_fields
23
+ build_source
24
+ build_total_hits_tracking
25
+ build_append_hash
26
+ end
27
+
28
+ elasticsearch_query.clean
29
+ elasticsearch_query
30
+ end
31
+
32
+ private
33
+
34
+ def build_search_criteria
35
+ search_by(search_criteria)
36
+ end
37
+
38
+ def build_context
39
+ return unless current_context
40
+
41
+ item = config[:contexts][current_context.to_s]
42
+ instance_exec(&item.block) if item
43
+ end
44
+
45
+ def build_defaults
46
+ keys = search_criteria_keys.map(&:to_s)
47
+
48
+ config[:defaults].each do |key, item|
49
+ instance_exec(&item.block) unless keys.include?(key.to_s)
50
+ end
51
+ end
52
+
53
+ def build_limits
54
+ elasticsearch_query[:size] = current_limit.to_i if @current_page || @current_limit
55
+ elasticsearch_query[:from] = current_offset if @current_page || @current_offset
56
+ end
57
+
58
+ def build_orders
59
+ return if current_limit.zero?
60
+
61
+ order_by(current_order || :default)
62
+ end
63
+
64
+ def build_suggestions
65
+ suggest_with(*current_suggestions) if current_suggestions
66
+ end
67
+
68
+ def build_fields
69
+ elasticsearch_query[:fields] = current_fields.map(&:to_s) if current_fields
70
+ end
71
+
72
+ def build_source
73
+ elasticsearch_query[:_source] = current_source unless current_source.nil?
74
+ end
75
+
76
+ def build_total_hits_tracking
77
+ elasticsearch_query[:track_total_hits] = @track_total_hits if @track_total_hits
78
+ end
79
+
80
+ def build_prepend_hash
81
+ elasticsearch_query.merge!(@prepend_hash) if @prepend_hash
82
+ end
83
+
84
+ def build_append_hash
85
+ elasticsearch_query.merge!(@append_hash) if @append_hash
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module QueryMethods
6
+ delegate :merge, :merge!, :to_h, :to_json, :as_json, :filters,
7
+ :add_none, :add_filter, :add_sort, :add_suggestion,
8
+ :build_terms, :should_filter_on, :must_filter_on, :must_not_filter_on,
9
+ :nested_queries, :nested_query,
10
+ to: :elasticsearch_query
11
+
12
+ # Accessors
13
+ # ------------------------------------------------------------------------
14
+ def elasticsearch_query
15
+ @elasticsearch_query ||= Caoutsearch::Search::Query::Base.new
16
+ end
17
+ alias_method :query, :elasticsearch_query
18
+
19
+ # Applying criteria
20
+ # ------------------------------------------------------------------------
21
+ def search_by(criteria)
22
+ case criteria
23
+ when Array then criteria.each { |criterion| search_by(criterion) }
24
+ when Hash then criteria.each { |key, value| filter_by(key, value) }
25
+ when String then apply_dsl_match_all(criteria)
26
+ end
27
+ end
28
+
29
+ def filter_by(key, value)
30
+ case key
31
+ when Array
32
+ items = key.filter_map { |k| config[:filters][k] }
33
+ apply_dsl_filters(items, value)
34
+ when Caoutsearch::Search::DSL::Item
35
+ apply_dsl_filter(key, value)
36
+ else
37
+ if config[:filters].key?(key)
38
+ apply_dsl_filter(config[:filters][key], value)
39
+ elsif config[:filters].key?(:__undef__)
40
+ block = config[:filters][:__undef__]
41
+ instance_exec(key, value, &block)
42
+ end
43
+ end
44
+ end
45
+
46
+ # Applying DSL filters items
47
+ # ------------------------------------------------------------------------
48
+ def apply_dsl_match_all(value)
49
+ return unless (block = config[:match_all])
50
+
51
+ instance_exec(value, &block)
52
+ end
53
+
54
+ def apply_dsl_filter(item, value)
55
+ return instance_exec(value, &item.block) if item.has_block?
56
+
57
+ terms = []
58
+ indexes = item.indexes
59
+ options = item.options.dup
60
+ query = elasticsearch_query
61
+
62
+ if options[:nested_query]
63
+ query = nested_query(options[:nested_query])
64
+ options[:nested] = nil
65
+ end
66
+
67
+ indexes.each do |index|
68
+ options_index = options.dup
69
+ options_index[:type] = mappings.find_type(index) unless options_index.key?(:type)
70
+ options_index[:nested] = mappings.nested_path(index) unless options_index.key?(:nested)
71
+ options_index[:include_in_parent] = mappings.include_in_parent?(index) unless options_index.key?(:include_in_parent) || !options_index[:nested]
72
+
73
+ terms += query.build_terms(index, value, **options_index)
74
+ end
75
+
76
+ query.should_filter_on(terms)
77
+ end
78
+
79
+ def apply_dsl_filters(items, value)
80
+ terms = []
81
+
82
+ items.each do |item|
83
+ terms += isolate_dsl_filter(item, value)
84
+ end
85
+
86
+ should_filter_on(terms)
87
+ end
88
+
89
+ def isolate_dsl_filter(item, value)
90
+ isolated_instance = clone
91
+ isolated_instance.apply_dsl_filter(item, value)
92
+ isolated_instance.elasticsearch_query.dig(:query, :bool, :filter) || []
93
+ end
94
+
95
+ # Applying orders
96
+ # ------------------------------------------------------------------------
97
+ def order_by(keys)
98
+ case keys
99
+ when Array then keys.each { |key| order_by(key) }
100
+ when Hash then keys.each { |key, direction| sort_by(key, direction) }
101
+ when String, Symbol then sort_by(keys)
102
+ end
103
+ end
104
+
105
+ def sort_by(key, direction = nil)
106
+ if config[:sorts].key?(key)
107
+ apply_dsl_sort(config[:sorts][key], direction)
108
+ elsif config[:sorts].key?(:__undef__)
109
+ block = config[:sorts][:__undef__]
110
+ instance_exec(key, direction, &block)
111
+ end
112
+ end
113
+
114
+ def apply_dsl_sort(item, direction)
115
+ return instance_exec(direction, &item.block) if item.has_block?
116
+
117
+ indexes = item.indexes
118
+ indexes.each do |index|
119
+ case index
120
+ when :default, "default"
121
+ sort_by(:default, direction)
122
+
123
+ when Hash
124
+ index.map do |key, value|
125
+ key_direction = (value.to_s == direction.to_s) ? :asc : :desc
126
+ add_sort(key, key_direction)
127
+ end
128
+
129
+ else
130
+ add_sort(index, direction)
131
+ end
132
+ end
133
+ end
134
+
135
+ # Applying Suggests
136
+ # ------------------------------------------------------------------------
137
+ def suggest_with(*args)
138
+ args.each do |(hash, options)|
139
+ raise ArgumentError unless hash.is_a?(Hash)
140
+
141
+ hash.each do |key, value|
142
+ next unless (item = config[:suggestions][key.to_s])
143
+
144
+ options ||= {}
145
+ apply_dsl_suggest(item, value, **options)
146
+ end
147
+ end
148
+ end
149
+
150
+ def apply_dsl_suggest(item, query, **options)
151
+ return instance_exec(query, **options, &item.block) if item.has_block?
152
+
153
+ add_suggestion(item.name, query, **options)
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module Records
6
+ def records(use: nil, skip_query_cache: false)
7
+ if use
8
+ records_adapter.call(use, hits, skip_query_cache: skip_query_cache)
9
+ else
10
+ @records ||= records_adapter.call(model, hits)
11
+ end
12
+ end
13
+
14
+ def records_adapter
15
+ if defined?(ActiveRecord::Base)
16
+ Adapter::ActiveRecord
17
+ else
18
+ raise NotImplementedError
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module Resettable
6
+ def spawn
7
+ clone
8
+ end
9
+
10
+ def clone
11
+ super.reset
12
+ end
13
+
14
+ def dup
15
+ super.reset
16
+ end
17
+
18
+ def reset
19
+ reset_variable(:@elasticsearch_query)
20
+ reset_variable(:@nested_queries)
21
+ reset_variable(:@raw_response)
22
+ reset_variable(:@response)
23
+ reset_variable(:@records)
24
+ reset_variable(:@total_count)
25
+ reset_variable(:@aggregations)
26
+ reset_variable(:@suggestions)
27
+ @loaded = false
28
+ self
29
+ end
30
+
31
+ private
32
+
33
+ def reset_variable(key)
34
+ remove_instance_variable(key) if instance_variable_defined?(key)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Caoutsearch
4
+ module Search
5
+ module Response
6
+ delegate :empty?, :size, :slice, :[], :to_a, :to_ary, to: :hits
7
+ delegate_missing_to :each
8
+
9
+ def load
10
+ @raw_response = perform_search_query(build.to_h)
11
+ @response = Caoutsearch::Response::Response.new(@raw_response)
12
+ @loaded = true
13
+ self
14
+ end
15
+
16
+ def loaded?
17
+ @loaded
18
+ end
19
+
20
+ def response
21
+ load unless loaded?
22
+ @response
23
+ end
24
+
25
+ def raw_response
26
+ load unless loaded?
27
+ @raw_response
28
+ end
29
+
30
+ def took
31
+ response["took"]
32
+ end
33
+
34
+ def timed_out
35
+ response["timed_out"]
36
+ end
37
+
38
+ def shards
39
+ response["_shards"]
40
+ end
41
+
42
+ def hits
43
+ response.dig("hits", "hits")
44
+ end
45
+
46
+ def max_score
47
+ response.dig("hits", "max_score")
48
+ end
49
+
50
+ def total
51
+ response.dig("hits", "total")
52
+ end
53
+
54
+ def total_count
55
+ if !@track_total_hits && (!loaded? || response.dig("hits", "total", "relation") == "gte")
56
+ @total_count ||= spawn.track_total_hits!(true).source!(false).limit!(0).total_count
57
+ else
58
+ response.dig("hits", "total", "value")
59
+ end
60
+ end
61
+
62
+ def total_pages
63
+ (total_count.to_f / current_limit).ceil
64
+ end
65
+
66
+ def ids
67
+ hits.map { |hit| hit["_id"] }
68
+ end
69
+
70
+ def aggregations
71
+ @aggregations ||= Caoutsearch::Response::Aggregations.new(response.aggregations, self)
72
+ end
73
+
74
+ def suggestions
75
+ @suggestions ||= Caoutsearch::Response::Suggestions.new(response.suggest)
76
+ end
77
+
78
+ def each(&block)
79
+ return to_enum(:each) { hits.size } unless block
80
+
81
+ hits.each(&block)
82
+ end
83
+
84
+ def perform_search_query(query)
85
+ request_payload = {
86
+ index: index_name,
87
+ body: query
88
+ }
89
+
90
+ instrument(:search) do |event_payload|
91
+ event_payload[:request] = request_payload
92
+ event_payload[:response] = client.search(request_payload)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end