chewy 0.0.1 → 0.1.0
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 +13 -5
- data/.gitignore +1 -0
- data/.travis.yml +5 -3
- data/CHANGELOG.md +75 -0
- data/README.md +487 -92
- data/Rakefile +3 -2
- data/chewy.gemspec +2 -2
- data/filters +76 -0
- data/lib/chewy.rb +5 -3
- data/lib/chewy/config.rb +36 -19
- data/lib/chewy/fields/base.rb +5 -1
- data/lib/chewy/index.rb +22 -10
- data/lib/chewy/index/actions.rb +13 -13
- data/lib/chewy/index/search.rb +7 -2
- data/lib/chewy/query.rb +382 -64
- data/lib/chewy/query/context.rb +174 -0
- data/lib/chewy/query/criteria.rb +127 -34
- data/lib/chewy/query/loading.rb +9 -9
- data/lib/chewy/query/nodes/and.rb +25 -0
- data/lib/chewy/query/nodes/base.rb +17 -0
- data/lib/chewy/query/nodes/bool.rb +32 -0
- data/lib/chewy/query/nodes/equal.rb +34 -0
- data/lib/chewy/query/nodes/exists.rb +20 -0
- data/lib/chewy/query/nodes/expr.rb +28 -0
- data/lib/chewy/query/nodes/field.rb +106 -0
- data/lib/chewy/query/nodes/missing.rb +20 -0
- data/lib/chewy/query/nodes/not.rb +25 -0
- data/lib/chewy/query/nodes/or.rb +25 -0
- data/lib/chewy/query/nodes/prefix.rb +18 -0
- data/lib/chewy/query/nodes/query.rb +20 -0
- data/lib/chewy/query/nodes/range.rb +63 -0
- data/lib/chewy/query/nodes/raw.rb +15 -0
- data/lib/chewy/query/nodes/regexp.rb +31 -0
- data/lib/chewy/query/nodes/script.rb +20 -0
- data/lib/chewy/query/pagination.rb +28 -22
- data/lib/chewy/railtie.rb +23 -0
- data/lib/chewy/rspec/update_index.rb +20 -3
- data/lib/chewy/type/adapter/active_record.rb +78 -5
- data/lib/chewy/type/adapter/base.rb +46 -0
- data/lib/chewy/type/adapter/object.rb +40 -8
- data/lib/chewy/type/base.rb +1 -1
- data/lib/chewy/type/import.rb +18 -44
- data/lib/chewy/type/observe.rb +24 -14
- data/lib/chewy/version.rb +1 -1
- data/lib/tasks/chewy.rake +27 -0
- data/spec/chewy/config_spec.rb +30 -12
- data/spec/chewy/fields/base_spec.rb +11 -5
- data/spec/chewy/index/actions_spec.rb +20 -20
- data/spec/chewy/index/search_spec.rb +5 -5
- data/spec/chewy/index_spec.rb +28 -8
- data/spec/chewy/query/context_spec.rb +173 -0
- data/spec/chewy/query/criteria_spec.rb +219 -12
- data/spec/chewy/query/loading_spec.rb +6 -4
- data/spec/chewy/query/nodes/and_spec.rb +16 -0
- data/spec/chewy/query/nodes/bool_spec.rb +22 -0
- data/spec/chewy/query/nodes/equal_spec.rb +32 -0
- data/spec/chewy/query/nodes/exists_spec.rb +18 -0
- data/spec/chewy/query/nodes/missing_spec.rb +15 -0
- data/spec/chewy/query/nodes/not_spec.rb +16 -0
- data/spec/chewy/query/nodes/or_spec.rb +16 -0
- data/spec/chewy/query/nodes/prefix_spec.rb +16 -0
- data/spec/chewy/query/nodes/query_spec.rb +12 -0
- data/spec/chewy/query/nodes/range_spec.rb +32 -0
- data/spec/chewy/query/nodes/raw_spec.rb +11 -0
- data/spec/chewy/query/nodes/regexp_spec.rb +31 -0
- data/spec/chewy/query/nodes/script_spec.rb +15 -0
- data/spec/chewy/query/pagination_spec.rb +3 -2
- data/spec/chewy/query_spec.rb +83 -26
- data/spec/chewy/rspec/update_index_spec.rb +20 -0
- data/spec/chewy/type/adapter/active_record_spec.rb +102 -0
- data/spec/chewy/type/adapter/object_spec.rb +82 -0
- data/spec/chewy/type/import_spec.rb +30 -1
- data/spec/chewy/type/mapping_spec.rb +1 -1
- data/spec/chewy/type/observe_spec.rb +46 -12
- data/spec/spec_helper.rb +7 -6
- data/spec/support/class_helpers.rb +2 -2
- metadata +98 -48
- data/.rvmrc +0 -1
- data/lib/chewy/index/client.rb +0 -13
- data/spec/chewy/index/client_spec.rb +0 -18
@@ -0,0 +1,174 @@
|
|
1
|
+
require 'chewy/query/nodes/base'
|
2
|
+
require 'chewy/query/nodes/expr'
|
3
|
+
require 'chewy/query/nodes/field'
|
4
|
+
require 'chewy/query/nodes/bool'
|
5
|
+
require 'chewy/query/nodes/and'
|
6
|
+
require 'chewy/query/nodes/or'
|
7
|
+
require 'chewy/query/nodes/not'
|
8
|
+
require 'chewy/query/nodes/raw'
|
9
|
+
require 'chewy/query/nodes/exists'
|
10
|
+
require 'chewy/query/nodes/missing'
|
11
|
+
require 'chewy/query/nodes/range'
|
12
|
+
require 'chewy/query/nodes/prefix'
|
13
|
+
require 'chewy/query/nodes/regexp'
|
14
|
+
require 'chewy/query/nodes/equal'
|
15
|
+
require 'chewy/query/nodes/query'
|
16
|
+
require 'chewy/query/nodes/script'
|
17
|
+
|
18
|
+
module Chewy
|
19
|
+
class Query
|
20
|
+
# Context provides simplified DSL functionality for filters declaring.
|
21
|
+
# You can use logic operations <tt>&</tt> and <tt>|</tt> to concat
|
22
|
+
# expressions.
|
23
|
+
#
|
24
|
+
# UsersIndex.filter{ (article.title =~ /Honey/) & (age < 42) & !rate }
|
25
|
+
#
|
26
|
+
#
|
27
|
+
class Context
|
28
|
+
def initialize &block
|
29
|
+
@block = block
|
30
|
+
@outer = eval('self', block.binding)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Outer scope call
|
34
|
+
# Block evaluates in the external context
|
35
|
+
#
|
36
|
+
# def name
|
37
|
+
# 'Friend'
|
38
|
+
# end
|
39
|
+
#
|
40
|
+
# UsersIndex.filter{ name == o{ name } } # => {filter: {term: {name: 'Friend'}}}
|
41
|
+
#
|
42
|
+
def o &block
|
43
|
+
@outer.instance_exec(&block)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Returns field node
|
47
|
+
# Used if method_missing is not working by some reason.
|
48
|
+
# Additional expression options might be passed as second argument hash.
|
49
|
+
#
|
50
|
+
# UsersIndex.filter{ f(:name) == 'Name' } == UsersIndex.filter{ name == 'Name' } # => true
|
51
|
+
# UsersIndex.filter{ f(:name, execution: :bool) == ['Name1', 'Name2'] } ==
|
52
|
+
# UsersIndex.filter{ name(execution: :bool) == ['Name1', 'Name2'] } # => true
|
53
|
+
#
|
54
|
+
# Supports block for getting field name from the outer scope
|
55
|
+
#
|
56
|
+
# def field
|
57
|
+
# :name
|
58
|
+
# end
|
59
|
+
#
|
60
|
+
# UsersIndex.filter{ f{ field } == 'Name' } == UsersIndex.filter{ name == 'Name' } # => true
|
61
|
+
#
|
62
|
+
def f name = nil, *args, &block
|
63
|
+
name = block ? o(&block) : name
|
64
|
+
Nodes::Field.new name, *args
|
65
|
+
end
|
66
|
+
|
67
|
+
# Returns script filter
|
68
|
+
# Just script filter. Supports additional params.
|
69
|
+
#
|
70
|
+
# UsersIndex.filter{ s('doc["num1"].value > 1') }
|
71
|
+
# UsersIndex.filter{ s('doc["num1"].value > param1', param1: 42) }
|
72
|
+
#
|
73
|
+
# Supports block for getting script from the outer scope
|
74
|
+
#
|
75
|
+
# def script
|
76
|
+
# 'doc["num1"].value > param1 || 1'
|
77
|
+
# end
|
78
|
+
#
|
79
|
+
# UsersIndex.filter{ s{ script } } == UsersIndex.filter{ s('doc["num1"].value > 1') } # => true
|
80
|
+
# UsersIndex.filter{ s(param1: 42) { script } } == UsersIndex.filter{ s('doc["num1"].value > 1', param1: 42) } # => true
|
81
|
+
#
|
82
|
+
def s *args, &block
|
83
|
+
params = args.extract_options!
|
84
|
+
script = block ? o(&block) : args.first
|
85
|
+
Nodes::Script.new script, params
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns query filter
|
89
|
+
#
|
90
|
+
# UsersIndex.filter{ q(query_string: {query: 'name: hello'}) }
|
91
|
+
#
|
92
|
+
# Supports block for getting query from the outer scope
|
93
|
+
#
|
94
|
+
# def query
|
95
|
+
# {query_string: {query: 'name: hello'}}
|
96
|
+
# end
|
97
|
+
#
|
98
|
+
# UsersIndex.filter{ q{ query } } == UsersIndex.filter{ q(query_string: {query: 'name: hello'}) } # => true
|
99
|
+
#
|
100
|
+
def q query = nil, &block
|
101
|
+
Nodes::Query.new block ? o(&block) : query
|
102
|
+
end
|
103
|
+
|
104
|
+
# Returns raw expression
|
105
|
+
# Same as filter with arguments instead of block, but can participate in expressions
|
106
|
+
#
|
107
|
+
# UsersIndex.filter{ r(term: {name: 'Name'}) }
|
108
|
+
# UsersIndex.filter{ r(term: {name: 'Name'}) & (age < 42) }
|
109
|
+
#
|
110
|
+
# Supports block for getting raw filter from the outer scope
|
111
|
+
#
|
112
|
+
# def filter
|
113
|
+
# {term: {name: 'Name'}}
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# UsersIndex.filter{ r{ filter } } == UsersIndex.filter{ r(term: {name: 'Name'}) } # => true
|
117
|
+
# UsersIndex.filter{ r{ filter } } == UsersIndex.filter(term: {name: 'Name'}) # => true
|
118
|
+
#
|
119
|
+
def r raw = nil, &block
|
120
|
+
Nodes::Raw.new block ? o(&block) : raw
|
121
|
+
end
|
122
|
+
|
123
|
+
# Bool filter chainable methods
|
124
|
+
# Used to create bool query. Nodes are passed as arguments.
|
125
|
+
#
|
126
|
+
# UsersIndex.filter{ must(age < 42, name == 'Name') }
|
127
|
+
# UsersIndex.filter{ should(age < 42, name == 'Name') }
|
128
|
+
# UsersIndex.filter{ must(age < 42).should(name == 'Name1', name == 'Name2') }
|
129
|
+
# UsersIndex.filter{ should_not(age >= 42).must(name == 'Name1') }
|
130
|
+
#
|
131
|
+
%w(must must_not should).each do |method|
|
132
|
+
define_method method do |*exprs|
|
133
|
+
Nodes::Bool.new.send(method, *exprs)
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
# Creates field or exists node
|
138
|
+
# Additional options for further expression might be passed as hash
|
139
|
+
#
|
140
|
+
# UsersIndex.filter{ name == 'Name' } == UsersIndex.filter(term: {name: 'Name'}) # => true
|
141
|
+
# UsersIndex.filter{ name? } == UsersIndex.filter(exists: {term: 'name'}) # => true
|
142
|
+
# UsersIndex.filter{ name(execution: :bool) == ['Name1', 'Name2'] } ==
|
143
|
+
# UsersIndex.filter(terms: {name: ['Name1', 'Name2'], execution: :bool}) # => true
|
144
|
+
#
|
145
|
+
# Also field names might be chained to use dot-notation for ES field names
|
146
|
+
#
|
147
|
+
# UsersIndex.filter{ article.title =~ 'Hello' }
|
148
|
+
# UsersIndex.filter{ article.tags? }
|
149
|
+
#
|
150
|
+
def method_missing method, *args, &block
|
151
|
+
method = method.to_s
|
152
|
+
if method =~ /\?\Z/
|
153
|
+
Nodes::Exists.new method.gsub(/\?\Z/, '')
|
154
|
+
else
|
155
|
+
f method, *args
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
# Evaluates context block, returns top node.
|
160
|
+
# For internal usage.
|
161
|
+
#
|
162
|
+
def __result__
|
163
|
+
instance_exec(&@block)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Renders evaluated filters.
|
167
|
+
# For internal usage.
|
168
|
+
#
|
169
|
+
def __render__
|
170
|
+
__result__.__render__ # haha, wtf?
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
174
|
+
end
|
data/lib/chewy/query/criteria.rb
CHANGED
@@ -1,53 +1,44 @@
|
|
1
1
|
module Chewy
|
2
2
|
class Query
|
3
3
|
class Criteria
|
4
|
-
STORAGES = [:
|
4
|
+
STORAGES = [:options, :queries, :facets, :filters, :sort, :fields, :types]
|
5
5
|
|
6
|
-
def
|
7
|
-
|
6
|
+
def initialize options = {}
|
7
|
+
@options = options.merge(query_mode: Chewy.query_mode, filter_mode: Chewy.filter_mode)
|
8
8
|
end
|
9
9
|
|
10
|
-
def
|
11
|
-
|
10
|
+
def == other
|
11
|
+
other.is_a?(self.class) && storages == other.storages
|
12
12
|
end
|
13
13
|
|
14
|
-
[:
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
[:filters, :sort, :fields].each do |storage|
|
23
|
-
class_eval <<-METHODS, __FILE__, __LINE__ + 1
|
24
|
-
def #{storage}
|
25
|
-
@#{storage} ||= []
|
26
|
-
end
|
27
|
-
METHODS
|
14
|
+
{ (STORAGES - [:options, :facets]) => '[]', [:options, :facets] => '{}' }.each do |storages, default|
|
15
|
+
storages.each do |storage|
|
16
|
+
class_eval <<-METHODS, __FILE__, __LINE__ + 1
|
17
|
+
def #{storage}
|
18
|
+
@#{storage} ||= #{default}
|
19
|
+
end
|
20
|
+
METHODS
|
21
|
+
end
|
28
22
|
end
|
29
23
|
|
30
24
|
STORAGES.each do |storage|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
end
|
35
|
-
METHODS
|
36
|
-
end
|
37
|
-
|
38
|
-
def update_search(modifer)
|
39
|
-
search.merge!(modifer)
|
25
|
+
define_method "#{storage}?" do
|
26
|
+
send(storage).any?
|
27
|
+
end
|
40
28
|
end
|
41
29
|
|
42
|
-
def
|
43
|
-
|
30
|
+
def update_options(modifer)
|
31
|
+
options.merge!(modifer)
|
44
32
|
end
|
45
33
|
|
46
|
-
|
47
34
|
def update_facets(modifer)
|
48
35
|
facets.merge!(modifer)
|
49
36
|
end
|
50
37
|
|
38
|
+
def update_queries(modifer)
|
39
|
+
@queries = queries + Array.wrap(modifer).delete_if(&:blank?)
|
40
|
+
end
|
41
|
+
|
51
42
|
def update_filters(modifer)
|
52
43
|
@filters = filters + Array.wrap(modifer).delete_if(&:blank?)
|
53
44
|
end
|
@@ -60,13 +51,42 @@ module Chewy
|
|
60
51
|
@sort = sort + modifer
|
61
52
|
end
|
62
53
|
|
63
|
-
|
64
|
-
|
65
|
-
|
54
|
+
%w(fields types).each do |storage|
|
55
|
+
define_method "update_#{storage}" do |modifer, options = {}|
|
56
|
+
variable = "@#{storage}"
|
57
|
+
instance_variable_set(variable, nil) if options[:purge]
|
58
|
+
modifer = send(storage) | Array.wrap(modifer).flatten.map(&:to_s).delete_if(&:blank?)
|
59
|
+
instance_variable_set(variable, modifer)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def merge! other
|
64
|
+
STORAGES.each do |storage|
|
65
|
+
send("update_#{storage}", other.send(storage))
|
66
|
+
end
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def merge other
|
71
|
+
clone.merge!(other)
|
72
|
+
end
|
73
|
+
|
74
|
+
def request_body
|
75
|
+
body = (_request_query || {}).tap do |body|
|
76
|
+
body.merge!(facets: facets) if facets?
|
77
|
+
body.merge!(sort: sort) if sort?
|
78
|
+
body.merge!(fields: fields) if fields?
|
79
|
+
end
|
80
|
+
|
81
|
+
{body: body.merge!(_request_options)}
|
66
82
|
end
|
67
83
|
|
68
84
|
protected
|
69
85
|
|
86
|
+
def storages
|
87
|
+
STORAGES.map { |storage| send(storage) }
|
88
|
+
end
|
89
|
+
|
70
90
|
def initialize_clone(other)
|
71
91
|
STORAGES.each do |storage|
|
72
92
|
value = other.send(storage)
|
@@ -76,6 +96,79 @@ module Chewy
|
|
76
96
|
end
|
77
97
|
end
|
78
98
|
end
|
99
|
+
|
100
|
+
def _request_options
|
101
|
+
options.slice(:size, :from, :explain)
|
102
|
+
end
|
103
|
+
|
104
|
+
def _request_query
|
105
|
+
request_filter = _request_filter
|
106
|
+
request_query = _queries_join(queries, options[:query_mode])
|
107
|
+
|
108
|
+
if request_filter
|
109
|
+
{query: {
|
110
|
+
filtered: {
|
111
|
+
query: request_query ? request_query : {match_all: {}},
|
112
|
+
filter: request_filter
|
113
|
+
}
|
114
|
+
}}
|
115
|
+
elsif request_query
|
116
|
+
{query: request_query}
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def _request_filter
|
121
|
+
filter_mode = options[:filter_mode]
|
122
|
+
request_filter = if filter_mode == :and
|
123
|
+
filters
|
124
|
+
else
|
125
|
+
[_filters_join(filters, filter_mode)]
|
126
|
+
end
|
127
|
+
|
128
|
+
_filters_join([_request_types, *request_filter], :and)
|
129
|
+
end
|
130
|
+
|
131
|
+
def _request_types
|
132
|
+
_filters_join(types.map { |type| {type: {value: type}} }, :or)
|
133
|
+
end
|
134
|
+
|
135
|
+
def _queries_join queries, logic
|
136
|
+
queries = queries.compact
|
137
|
+
|
138
|
+
if queries.many?
|
139
|
+
case logic
|
140
|
+
when :dis_max
|
141
|
+
{dis_max: {queries: queries}}
|
142
|
+
when :must, :should
|
143
|
+
{bool: {logic => queries}}
|
144
|
+
else
|
145
|
+
if logic.is_a?(Float)
|
146
|
+
{dis_max: {queries: queries, tie_breaker: logic}}
|
147
|
+
else
|
148
|
+
{bool: {should: queries, minimum_should_match: logic}}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
else
|
152
|
+
queries.first
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def _filters_join filters, logic
|
157
|
+
filters = filters.compact
|
158
|
+
|
159
|
+
if filters.many?
|
160
|
+
case logic
|
161
|
+
when :and, :or
|
162
|
+
{logic => filters}
|
163
|
+
when :must, :should
|
164
|
+
{bool: {logic => filters}}
|
165
|
+
else
|
166
|
+
{bool: {should: filters, minimum_should_match: logic}}
|
167
|
+
end
|
168
|
+
else
|
169
|
+
filters.first
|
170
|
+
end
|
171
|
+
end
|
79
172
|
end
|
80
173
|
end
|
81
174
|
end
|
data/lib/chewy/query/loading.rb
CHANGED
@@ -4,23 +4,23 @@ module Chewy
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
def load(options = {})
|
7
|
-
::Kaminari
|
8
|
-
|
7
|
+
if defined?(::Kaminari)
|
8
|
+
::Kaminari.paginate_array(_load_objects(options),
|
9
|
+
limit: limit_value, offset: offset_value, total_count: total_count)
|
10
|
+
else
|
11
|
+
_load_objects(options)
|
12
|
+
end
|
9
13
|
end
|
10
14
|
|
11
15
|
private
|
12
16
|
|
13
17
|
def _load_objects(options)
|
14
18
|
loaded_objects = Hash[_results.group_by(&:class).map do |type, objects|
|
15
|
-
|
16
|
-
|
17
|
-
additional_scope = options[:scopes][type.type_name.to_sym] if options[:scopes]
|
18
|
-
scope = scope.instance_eval(&additional_scope) if additional_scope
|
19
|
-
|
20
|
-
[type, scope.index_by(&:id)]
|
19
|
+
loaded = type.adapter.load(objects, options[type.type_name.to_sym] || {})
|
20
|
+
[type, loaded.index_by.with_index { |loaded, i| objects[i] }]
|
21
21
|
end]
|
22
22
|
|
23
|
-
_results.map { |result| loaded_objects[result.class][result
|
23
|
+
_results.map { |result| loaded_objects[result.class][result] }
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Chewy
|
2
|
+
class Query
|
3
|
+
module Nodes
|
4
|
+
class And < Expr
|
5
|
+
def initialize *nodes
|
6
|
+
@options = nodes.extract_options!
|
7
|
+
@nodes = nodes.flatten.map { |node| node.is_a?(self.class) ? node.__nodes__ : node }.flatten
|
8
|
+
end
|
9
|
+
|
10
|
+
def __nodes__
|
11
|
+
@nodes
|
12
|
+
end
|
13
|
+
|
14
|
+
def __render__
|
15
|
+
nodes = @nodes.map(&:__render__)
|
16
|
+
if @options.key?(:cache)
|
17
|
+
{and: {filters: nodes, _cache: !!@options[:cache]}}
|
18
|
+
else
|
19
|
+
{and: nodes}
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Chewy
|
2
|
+
class Query
|
3
|
+
module Nodes
|
4
|
+
class Base
|
5
|
+
def render
|
6
|
+
raise NotImplementedError
|
7
|
+
end
|
8
|
+
|
9
|
+
def eql? other
|
10
|
+
other.is_a?(self.class) && instance_variables.all? do |ivar|
|
11
|
+
instance_variable_get(ivar).eql? other.instance_variable_get(ivar)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|