caoutsearch 0.0.1 → 0.0.4

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 (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 +71 -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 +45 -127
  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