caoutsearch 0.0.0 → 0.0.3
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.
- checksums.yaml +4 -4
- data/README.md +819 -6
- data/lib/caoutsearch/config/mappings.rb +1 -1
- data/lib/caoutsearch/filter/base.rb +11 -7
- data/lib/caoutsearch/filter/boolean.rb +1 -1
- data/lib/caoutsearch/filter/date.rb +93 -22
- data/lib/caoutsearch/filter/default.rb +10 -10
- data/lib/caoutsearch/filter/geo_point.rb +1 -1
- data/lib/caoutsearch/filter/match.rb +5 -5
- data/lib/caoutsearch/filter/none.rb +1 -1
- data/lib/caoutsearch/filter/range.rb +6 -6
- data/lib/caoutsearch/index/document.rb +11 -11
- data/lib/caoutsearch/index/indice_versions.rb +3 -3
- data/lib/caoutsearch/index/internal_dsl.rb +3 -3
- data/lib/caoutsearch/index/reindex.rb +11 -11
- data/lib/caoutsearch/index/scoping.rb +4 -4
- data/lib/caoutsearch/index/serialization.rb +13 -13
- data/lib/caoutsearch/instrumentation/base.rb +12 -12
- data/lib/caoutsearch/instrumentation/search.rb +11 -2
- data/lib/caoutsearch/mappings.rb +1 -1
- data/lib/caoutsearch/model/indexable.rb +57 -0
- data/lib/caoutsearch/model/searchable.rb +31 -0
- data/lib/caoutsearch/model.rb +12 -0
- data/lib/caoutsearch/response/aggregations.rb +50 -0
- data/lib/caoutsearch/response/response.rb +9 -0
- data/lib/caoutsearch/response/suggestions.rb +9 -0
- data/lib/caoutsearch/response.rb +6 -0
- data/lib/caoutsearch/search/adapter/active_record.rb +39 -0
- data/lib/caoutsearch/search/base.rb +16 -15
- data/lib/caoutsearch/search/batch/scroll.rb +93 -0
- data/lib/caoutsearch/search/batch/search_after.rb +70 -0
- data/lib/caoutsearch/search/batch_methods.rb +63 -0
- data/lib/caoutsearch/search/callbacks.rb +28 -0
- data/lib/caoutsearch/search/delete_methods.rb +19 -0
- data/lib/caoutsearch/search/dsl/item.rb +2 -2
- data/lib/caoutsearch/search/inspect.rb +34 -0
- data/lib/caoutsearch/search/instrumentation.rb +19 -0
- data/lib/caoutsearch/search/internal_dsl.rb +107 -0
- data/lib/caoutsearch/search/naming.rb +45 -0
- data/lib/caoutsearch/search/point_in_time.rb +28 -0
- data/lib/caoutsearch/search/query/boolean.rb +4 -4
- data/lib/caoutsearch/search/query/nested.rb +1 -1
- data/lib/caoutsearch/search/query/setters.rb +4 -4
- data/lib/caoutsearch/search/query_builder/aggregations.rb +49 -0
- data/lib/caoutsearch/search/query_builder.rb +89 -0
- data/lib/caoutsearch/search/query_methods.rb +157 -0
- data/lib/caoutsearch/search/records.rb +23 -0
- data/lib/caoutsearch/search/resettable.rb +38 -0
- data/lib/caoutsearch/search/response.rb +97 -0
- data/lib/caoutsearch/search/sanitizer.rb +2 -2
- data/lib/caoutsearch/search/search_methods.rb +239 -0
- data/lib/caoutsearch/search/type_cast.rb +14 -6
- data/lib/caoutsearch/search/value.rb +10 -10
- data/lib/caoutsearch/search/value_overflow.rb +1 -1
- data/lib/caoutsearch/settings.rb +1 -1
- data/lib/caoutsearch/testing/mock_requests.rb +105 -0
- data/lib/caoutsearch/testing.rb +3 -0
- data/lib/caoutsearch/version.rb +1 -1
- data/lib/caoutsearch.rb +10 -5
- metadata +44 -126
- data/lib/caoutsearch/search/search/delete_methods.rb +0 -21
- data/lib/caoutsearch/search/search/inspect.rb +0 -36
- data/lib/caoutsearch/search/search/instrumentation.rb +0 -21
- data/lib/caoutsearch/search/search/internal_dsl.rb +0 -77
- data/lib/caoutsearch/search/search/naming.rb +0 -47
- data/lib/caoutsearch/search/search/query_builder.rb +0 -94
- data/lib/caoutsearch/search/search/query_methods.rb +0 -180
- data/lib/caoutsearch/search/search/resettable.rb +0 -35
- data/lib/caoutsearch/search/search/response.rb +0 -88
- data/lib/caoutsearch/search/search/scroll_methods.rb +0 -113
- 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
|
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 << {
|
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
|
-
{
|
41
|
+
{bool: {must_not: terms[0]}}
|
42
42
|
else
|
43
|
-
|
43
|
+
{bool: {must_not: terms}}
|
44
44
|
end
|
45
45
|
end
|
46
46
|
|
@@ -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.
|
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:
|
57
|
+
prefix: value,
|
58
58
|
completion: options.reverse_merge(
|
59
|
-
field:
|
59
|
+
field: key,
|
60
60
|
skip_duplicates: true,
|
61
|
-
fuzzy:
|
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
|