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
data/Rakefile
CHANGED
data/chewy.gemspec
CHANGED
@@ -18,13 +18,13 @@ Gem::Specification.new do |spec|
|
|
18
18
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
19
|
spec.require_paths = ['lib']
|
20
20
|
|
21
|
-
spec.add_development_dependency 'bundler'
|
22
21
|
spec.add_development_dependency 'rake'
|
23
|
-
spec.add_development_dependency 'rspec'
|
22
|
+
spec.add_development_dependency 'rspec', '~> 2.14'
|
24
23
|
spec.add_development_dependency 'sqlite3'
|
25
24
|
spec.add_development_dependency 'kaminari'
|
26
25
|
spec.add_development_dependency 'activerecord', '>= 3.2'
|
27
26
|
spec.add_development_dependency 'database_cleaner'
|
27
|
+
spec.add_development_dependency 'elasticsearch-extensions'
|
28
28
|
spec.add_development_dependency 'rubysl', '~> 2.0' if RUBY_ENGINE == 'rbx'
|
29
29
|
|
30
30
|
spec.add_dependency 'activesupport', '>= 3.2'
|
data/filters
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
term
|
2
|
+
name == 'value'
|
3
|
+
name != 'value'
|
4
|
+
terms
|
5
|
+
name == ['value1', 'value2'] plain
|
6
|
+
name != ['value1', 'value2']
|
7
|
+
|
8
|
+
name(:&) == ['value1', 'value2'] or
|
9
|
+
name(:|) == ['value1', 'value2'] and
|
10
|
+
name(:b) == ['value1', 'value2'] bool
|
11
|
+
name(:f) == ['value1', 'value2'] fielddata
|
12
|
+
regexp
|
13
|
+
name == /regexp/
|
14
|
+
name =~ /regexp/
|
15
|
+
name != /regexp/
|
16
|
+
name !~ /regexp/
|
17
|
+
name(:anystring, :intersection) == /regexp/
|
18
|
+
prefix
|
19
|
+
name =~ 'pref'
|
20
|
+
name !~ 'pref'
|
21
|
+
|
22
|
+
exists
|
23
|
+
name?
|
24
|
+
missing
|
25
|
+
!name
|
26
|
+
!name?
|
27
|
+
name == nil
|
28
|
+
|
29
|
+
numeric_range Numeric
|
30
|
+
range Other
|
31
|
+
date >= Date.today
|
32
|
+
date > Date.today
|
33
|
+
date <= Date.today
|
34
|
+
date < Date.today
|
35
|
+
|
36
|
+
date == (2.days.ago..3.days.since) ()
|
37
|
+
date == [2.days.ago..3.days.since] []
|
38
|
+
|
39
|
+
bool
|
40
|
+
must(name == 'name', email == 'email')
|
41
|
+
.should(name == 'name', email == 'email')
|
42
|
+
.must_not(name == 'name', email == 'email')
|
43
|
+
and
|
44
|
+
(name == 'name') & (email == 'email')
|
45
|
+
or
|
46
|
+
(name == 'name') | (email == 'email')
|
47
|
+
not
|
48
|
+
!(name == 'name')
|
49
|
+
email != 'email'
|
50
|
+
|
51
|
+
script s()
|
52
|
+
query q()
|
53
|
+
|
54
|
+
has child
|
55
|
+
has_child('type').query()
|
56
|
+
has_child('type').filter()
|
57
|
+
has parent
|
58
|
+
has_parent('type').query()
|
59
|
+
has_parent('type').filter()
|
60
|
+
nested
|
61
|
+
name.nested()
|
62
|
+
match all
|
63
|
+
match_all
|
64
|
+
|
65
|
+
geo bounding box
|
66
|
+
geo distance
|
67
|
+
geo distance range
|
68
|
+
geo polygon
|
69
|
+
geoshape
|
70
|
+
geohash cell
|
71
|
+
|
72
|
+
indices
|
73
|
+
type
|
74
|
+
ids
|
75
|
+
|
76
|
+
limit
|
data/lib/chewy.rb
CHANGED
@@ -14,6 +14,8 @@ require 'chewy/fields/base'
|
|
14
14
|
require 'chewy/fields/default'
|
15
15
|
require 'chewy/fields/root'
|
16
16
|
|
17
|
+
require 'chewy/railtie' if defined?(::Rails)
|
18
|
+
|
17
19
|
ActiveSupport.on_load(:active_record) do
|
18
20
|
extend Chewy::Type::Observe::ActiveRecordMethods
|
19
21
|
end
|
@@ -39,9 +41,9 @@ module Chewy
|
|
39
41
|
index = class_name.safe_constantize
|
40
42
|
raise Chewy::UnderivableType.new("Can not find index named `#{class_name}`") unless index && index < Chewy::Index
|
41
43
|
type = if type_name.present?
|
42
|
-
index.
|
43
|
-
elsif index.types.
|
44
|
-
index.types.
|
44
|
+
index.type_hash[type_name] or raise Chewy::UnderivableType.new("Index `#{class_name}` doesn`t have type named `#{type_name}`")
|
45
|
+
elsif index.types.one?
|
46
|
+
index.types.first
|
45
47
|
else
|
46
48
|
raise Chewy::UnderivableType.new("Index `#{class_name}` has more than one type, please specify type via `#{index_name}#type_name`")
|
47
49
|
end
|
data/lib/chewy/config.rb
CHANGED
@@ -2,46 +2,63 @@ module Chewy
|
|
2
2
|
class Config
|
3
3
|
include Singleton
|
4
4
|
|
5
|
-
attr_accessor :
|
5
|
+
attr_accessor :client_options, :urgent_update, :query_mode, :filter_mode, :logger
|
6
6
|
|
7
7
|
def self.delegated
|
8
8
|
public_instance_methods - self.superclass.public_instance_methods - Singleton.public_instance_methods
|
9
9
|
end
|
10
10
|
|
11
11
|
def initialize
|
12
|
-
@
|
12
|
+
@urgent_update = false
|
13
13
|
@client_options = {}
|
14
|
+
@query_mode = :must
|
15
|
+
@filter_mode = :and
|
14
16
|
end
|
15
17
|
|
16
18
|
def client_options
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
19
|
+
options = @client_options.merge(yaml_options)
|
20
|
+
options.merge!(logger: logger) if logger
|
21
|
+
options
|
22
|
+
end
|
23
|
+
|
24
|
+
def client?
|
25
|
+
!!Thread.current[:chewy_client]
|
26
|
+
end
|
27
|
+
|
28
|
+
def client
|
29
|
+
Thread.current[:chewy_client] ||= ::Elasticsearch::Client.new client_options
|
22
30
|
end
|
23
31
|
|
24
32
|
def atomic?
|
25
|
-
|
33
|
+
stash.any?
|
26
34
|
end
|
27
35
|
|
28
36
|
def atomic
|
29
|
-
|
30
|
-
|
31
|
-
atomic_stash.last.each { |type, ids| type.import(ids) }
|
32
|
-
result
|
37
|
+
stash.push({})
|
38
|
+
yield
|
33
39
|
ensure
|
34
|
-
|
40
|
+
stash.pop.each { |type, ids| type.import(ids) }
|
35
41
|
end
|
36
42
|
|
37
|
-
def
|
38
|
-
if
|
43
|
+
def stash *args
|
44
|
+
if args.any?
|
45
|
+
type, ids = *args
|
39
46
|
raise ArgumentError.new('Only Chewy::Type::Base accepted as the first argument') unless type < Chewy::Type::Base
|
40
|
-
|
41
|
-
|
42
|
-
atomic_stash.last[type] |= ids.flatten
|
47
|
+
stash.last[type] ||= []
|
48
|
+
stash.last[type] |= ids
|
43
49
|
else
|
44
|
-
Thread.current[:
|
50
|
+
Thread.current[:chewy_cache] ||= []
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
private
|
55
|
+
|
56
|
+
def yaml_options
|
57
|
+
@yaml_options ||= begin
|
58
|
+
if defined?(Rails)
|
59
|
+
file = Rails.root.join(*%w(config chewy.yml))
|
60
|
+
YAML.load_file(file)[Rails.env].try(:deep_symbolize_keys) if File.exists?(file)
|
61
|
+
end || {}
|
45
62
|
end
|
46
63
|
end
|
47
64
|
end
|
data/lib/chewy/fields/base.rb
CHANGED
@@ -13,7 +13,11 @@ module Chewy
|
|
13
13
|
end
|
14
14
|
|
15
15
|
def compose(object)
|
16
|
-
result = value
|
16
|
+
result = if value && value.is_a?(Proc)
|
17
|
+
value.arity == 0 ? object.instance_exec(&value) : value.call(object)
|
18
|
+
else
|
19
|
+
object.send(name)
|
20
|
+
end
|
17
21
|
|
18
22
|
result = if result.is_a?(Enumerable)
|
19
23
|
result.map { |object| nested_compose(object) }
|
data/lib/chewy/index.rb
CHANGED
@@ -1,30 +1,42 @@
|
|
1
1
|
require 'chewy/index/actions'
|
2
|
-
require 'chewy/index/client'
|
3
2
|
require 'chewy/index/search'
|
4
3
|
|
5
4
|
module Chewy
|
6
5
|
class Index
|
7
6
|
include Actions
|
8
|
-
include Client
|
9
7
|
include Search
|
10
8
|
|
11
|
-
|
12
|
-
|
9
|
+
singleton_class.delegate :client, to: 'Chewy'
|
10
|
+
|
11
|
+
class_attribute :type_hash
|
12
|
+
self.type_hash = {}
|
13
13
|
|
14
14
|
class_attribute :_settings
|
15
15
|
self._settings = {}
|
16
16
|
|
17
17
|
def self.define_type(name_or_scope, &block)
|
18
18
|
type_class = Chewy::Type.new(self, name_or_scope, &block)
|
19
|
-
self.
|
19
|
+
self.type_hash = type_hash.merge(type_class.type_name => type_class)
|
20
20
|
|
21
21
|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
22
22
|
def self.#{type_class.type_name}
|
23
|
-
|
23
|
+
type_hash['#{type_class.type_name}']
|
24
24
|
end
|
25
25
|
RUBY
|
26
26
|
end
|
27
27
|
|
28
|
+
def self.types *args
|
29
|
+
if args.any?
|
30
|
+
all.types *args
|
31
|
+
else
|
32
|
+
type_hash.values
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.type_names
|
37
|
+
type_hash.keys
|
38
|
+
end
|
39
|
+
|
28
40
|
def self.settings(params)
|
29
41
|
self._settings = params
|
30
42
|
end
|
@@ -43,7 +55,7 @@ module Chewy
|
|
43
55
|
end
|
44
56
|
|
45
57
|
def self.mappings_hash
|
46
|
-
mappings = types.
|
58
|
+
mappings = types.map(&:mappings_hash).inject(:merge)
|
47
59
|
mappings.present? ? {mappings: mappings} : {}
|
48
60
|
end
|
49
61
|
|
@@ -56,15 +68,15 @@ module Chewy
|
|
56
68
|
end
|
57
69
|
|
58
70
|
def self.search_type
|
59
|
-
|
71
|
+
type_names
|
60
72
|
end
|
61
73
|
|
62
74
|
def self.import
|
63
|
-
types.
|
75
|
+
types.all? { |t| t.import }
|
64
76
|
end
|
65
77
|
|
66
78
|
def self.reset
|
67
|
-
|
79
|
+
purge!
|
68
80
|
import
|
69
81
|
end
|
70
82
|
end
|
data/lib/chewy/index/actions.rb
CHANGED
@@ -4,38 +4,38 @@ module Chewy
|
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
6
|
module ClassMethods
|
7
|
-
def
|
7
|
+
def exists?
|
8
8
|
client.indices.exists(index: index_name)
|
9
9
|
end
|
10
10
|
|
11
|
-
def
|
12
|
-
|
11
|
+
def create
|
12
|
+
create!
|
13
13
|
rescue Elasticsearch::Transport::Transport::Errors::BadRequest
|
14
14
|
false
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
17
|
+
def create!
|
18
18
|
client.indices.create(index: index_name, body: index_params)
|
19
19
|
end
|
20
20
|
|
21
|
-
def
|
22
|
-
|
21
|
+
def delete
|
22
|
+
delete!
|
23
23
|
rescue Elasticsearch::Transport::Transport::Errors::NotFound
|
24
24
|
false
|
25
25
|
end
|
26
26
|
|
27
|
-
def
|
27
|
+
def delete!
|
28
28
|
client.indices.delete(index: index_name)
|
29
29
|
end
|
30
30
|
|
31
|
-
def
|
32
|
-
|
33
|
-
|
31
|
+
def purge
|
32
|
+
delete
|
33
|
+
create
|
34
34
|
end
|
35
35
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
36
|
+
def purge!
|
37
|
+
delete
|
38
|
+
create!
|
39
39
|
end
|
40
40
|
end
|
41
41
|
end
|
data/lib/chewy/index/search.rb
CHANGED
@@ -3,9 +3,14 @@ module Chewy
|
|
3
3
|
module Search
|
4
4
|
extend ActiveSupport::Concern
|
5
5
|
|
6
|
+
included do
|
7
|
+
singleton_class.delegate :explain, :limit, :offset, :facets, :query,
|
8
|
+
:filter, :order, :reorder, :only, :types, to: :all
|
9
|
+
end
|
10
|
+
|
6
11
|
module ClassMethods
|
7
|
-
def
|
8
|
-
Chewy::Query.new(search_index,
|
12
|
+
def all
|
13
|
+
Chewy::Query.new(search_index, types: search_type)
|
9
14
|
end
|
10
15
|
|
11
16
|
def search_string query, options = {}
|
data/lib/chewy/query.rb
CHANGED
@@ -1,132 +1,450 @@
|
|
1
|
+
begin
|
2
|
+
require 'kaminari'
|
3
|
+
rescue LoadError
|
4
|
+
end
|
5
|
+
|
1
6
|
require 'chewy/query/criteria'
|
7
|
+
require 'chewy/query/context'
|
2
8
|
require 'chewy/query/loading'
|
3
9
|
require 'chewy/query/pagination'
|
4
10
|
|
5
11
|
module Chewy
|
12
|
+
# Query allows you to create ES search requests with convenient
|
13
|
+
# chainable DSL. Queries are lazy evaluated and might be merged.
|
14
|
+
# The same DSL is used for whole index or individual types query build.
|
15
|
+
#
|
16
|
+
# UsersIndex.filter{ age < 42 }.query(text: {name: 'Alex'}).limit(20)
|
17
|
+
# UsersIndex::User.filter{ age < 42 }.query(text: {name: 'Alex'}).limit(20)
|
18
|
+
#
|
6
19
|
class Query
|
7
20
|
include Enumerable
|
8
21
|
include Loading
|
9
22
|
include Pagination
|
10
23
|
|
11
|
-
|
12
|
-
|
13
|
-
delegate :each, to: :_results
|
24
|
+
delegate :each, :count, :size, to: :_results
|
14
25
|
alias_method :to_ary, :to_a
|
15
26
|
|
16
27
|
attr_reader :index, :options, :criteria
|
17
28
|
|
18
|
-
def initialize
|
19
|
-
@index, @options = index,
|
29
|
+
def initialize index, options = {}
|
30
|
+
@index, @options = index, options
|
31
|
+
@types = Array.wrap(options.delete(:types))
|
20
32
|
@criteria = Criteria.new
|
21
33
|
reset
|
22
34
|
end
|
23
35
|
|
24
|
-
|
25
|
-
|
36
|
+
# Comparation with other query or collection
|
37
|
+
# If other is collection - search request is executed and
|
38
|
+
# result is used for comparation
|
39
|
+
#
|
40
|
+
# UsersIndex.filter(term: {name: 'Johny'}) == UsersIndex.filter(term: {name: 'Johny'}) # => true
|
41
|
+
# UsersIndex.filter(term: {name: 'Johny'}) == UsersIndex.filter(term: {name: 'Johny'}).to_a # => true
|
42
|
+
# UsersIndex.filter(term: {name: 'Johny'}) == UsersIndex.filter(term: {name: 'Winnie'}) # => false
|
43
|
+
#
|
44
|
+
def == other
|
45
|
+
super || if other.is_a?(self.class)
|
26
46
|
other.criteria == criteria
|
27
47
|
else
|
28
48
|
to_a == other
|
29
49
|
end
|
30
50
|
end
|
31
51
|
|
32
|
-
|
33
|
-
|
52
|
+
# Adds <tt>explain</tt> parameter to search request.
|
53
|
+
#
|
54
|
+
# UsersIndex.filter(term: {name: 'Johny'}).explain
|
55
|
+
# UsersIndex.filter(term: {name: 'Johny'}).explain(true)
|
56
|
+
# UsersIndex.filter(term: {name: 'Johny'}).explain(false)
|
57
|
+
#
|
58
|
+
# Calling explain without any arguments sets explanation flag to true.
|
59
|
+
# With <tt>explain: true</tt>, every result object has <tt>_explanation</tt>
|
60
|
+
# method
|
61
|
+
#
|
62
|
+
# UsersIndex::User.filter(term: {name: 'Johny'}).explain.first._explanation # => {...}
|
63
|
+
#
|
64
|
+
def explain value = nil
|
65
|
+
chain { criteria.update_options explain: (value.nil? ? true : value) }
|
66
|
+
end
|
67
|
+
|
68
|
+
# Sets query compilation mode for search request.
|
69
|
+
# Not used if only one filter for search is specified.
|
70
|
+
# Possible values:
|
71
|
+
#
|
72
|
+
# * <tt>:must</tt>
|
73
|
+
# Default value. Query compiles into a bool <tt>must</tt> query.
|
74
|
+
#
|
75
|
+
# Ex:
|
76
|
+
#
|
77
|
+
# UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}})
|
78
|
+
# # => {body: {
|
79
|
+
# query: {bool: {must: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
|
80
|
+
# }}
|
81
|
+
#
|
82
|
+
# * <tt>:should</tt>
|
83
|
+
# Query compiles into a bool <tt>should</tt> query.
|
84
|
+
#
|
85
|
+
# Ex:
|
86
|
+
#
|
87
|
+
# UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode(:should)
|
88
|
+
# # => {body: {
|
89
|
+
# query: {bool: {should: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
|
90
|
+
# }}
|
91
|
+
#
|
92
|
+
# * Any acceptable <tt>minimum_should_match</tt> value (1, '2', '75%')
|
93
|
+
# Query compiles into a bool <tt>should</tt> query with <tt>minimum_should_match</tt> set.
|
94
|
+
#
|
95
|
+
# Ex:
|
96
|
+
#
|
97
|
+
# UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode('50%')
|
98
|
+
# # => {body: {
|
99
|
+
# query: {bool: {
|
100
|
+
# should: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}],
|
101
|
+
# minimum_should_match: '50%'
|
102
|
+
# }}
|
103
|
+
# }}
|
104
|
+
#
|
105
|
+
# * <tt>:dis_max</tt>
|
106
|
+
# Query compiles into a <tt>dis_max</tt> query.
|
107
|
+
#
|
108
|
+
# Ex:
|
109
|
+
#
|
110
|
+
# UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode(:dis_max)
|
111
|
+
# # => {body: {
|
112
|
+
# query: {dis_max: {queries: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
|
113
|
+
# }}
|
114
|
+
#
|
115
|
+
# * Any Float value (0.0, 0.7, 1.0)
|
116
|
+
# Query compiles into a <tt>dis_max</tt> query with <tt>tie_breaker</tt> option set.
|
117
|
+
#
|
118
|
+
# Ex:
|
119
|
+
#
|
120
|
+
# UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}}).query_mode(0.7)
|
121
|
+
# # => {body: {
|
122
|
+
# query: {dis_max: {
|
123
|
+
# queries: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}],
|
124
|
+
# tie_breaker: 0.7
|
125
|
+
# }}
|
126
|
+
# }}
|
127
|
+
#
|
128
|
+
# Default value for <tt>:query_mode</tt> might be changed
|
129
|
+
# with <tt>Chewy.query_mode</tt> config option.
|
130
|
+
#
|
131
|
+
# Chewy.query_mode = :dis_max
|
132
|
+
# Chewy.query_mode = '50%'
|
133
|
+
#
|
134
|
+
def query_mode value
|
135
|
+
chain { criteria.update_options query_mode: value }
|
34
136
|
end
|
35
137
|
|
36
|
-
|
37
|
-
|
138
|
+
# Sets query compilation mode for search request.
|
139
|
+
# Not used if only one filter for search is specified.
|
140
|
+
# Possible values:
|
141
|
+
#
|
142
|
+
# * <tt>:and</tt>
|
143
|
+
# Default value. Filter compiles into an <tt>and</tt> filter.
|
144
|
+
#
|
145
|
+
# Ex:
|
146
|
+
#
|
147
|
+
# UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }
|
148
|
+
# # => {body: {query: {filtered: {
|
149
|
+
# query: {...},
|
150
|
+
# filter: {and: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
|
151
|
+
# }}}}
|
152
|
+
#
|
153
|
+
# * <tt>:or</tt>
|
154
|
+
# Filter compiles into an <tt>or</tt> filter.
|
155
|
+
#
|
156
|
+
# Ex:
|
157
|
+
#
|
158
|
+
# UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode(:or)
|
159
|
+
# # => {body: {query: {filtered: {
|
160
|
+
# query: {...},
|
161
|
+
# filter: {or: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
|
162
|
+
# }}}}
|
163
|
+
#
|
164
|
+
# * <tt>:must</tt>
|
165
|
+
# Filter compiles into a bool <tt>must</tt> filter.
|
166
|
+
#
|
167
|
+
# Ex:
|
168
|
+
#
|
169
|
+
# UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode(:must)
|
170
|
+
# # => {body: {query: {filtered: {
|
171
|
+
# query: {...},
|
172
|
+
# filter: {bool: {must: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
|
173
|
+
# }}}}
|
174
|
+
#
|
175
|
+
# * <tt>:should</tt>
|
176
|
+
# Filter compiles into a bool <tt>should</tt> filter.
|
177
|
+
#
|
178
|
+
# Ex:
|
179
|
+
#
|
180
|
+
# UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode(:should)
|
181
|
+
# # => {body: {query: {filtered: {
|
182
|
+
# query: {...},
|
183
|
+
# filter: {bool: {should: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
|
184
|
+
# }}}}
|
185
|
+
#
|
186
|
+
# * Any acceptable <tt>minimum_should_match</tt> value (1, '2', '75%')
|
187
|
+
# Filter compiles into bool <tt>should</tt> filter with <tt>minimum_should_match</tt> set.
|
188
|
+
#
|
189
|
+
# Ex:
|
190
|
+
#
|
191
|
+
# UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }.filter_mode('50%')
|
192
|
+
# # => {body: {query: {filtered: {
|
193
|
+
# query: {...},
|
194
|
+
# filter: {bool: {
|
195
|
+
# should: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}],
|
196
|
+
# minimum_should_match: '50%'
|
197
|
+
# }}
|
198
|
+
# }}}}
|
199
|
+
#
|
200
|
+
# Default value for <tt>:filter_mode</tt> might be changed
|
201
|
+
# with <tt>Chewy.filter_mode</tt> config option.
|
202
|
+
#
|
203
|
+
# Chewy.filter_mode = :should
|
204
|
+
# Chewy.filter_mode = '50%'
|
205
|
+
#
|
206
|
+
def filter_mode value
|
207
|
+
chain { criteria.update_options filter_mode: value }
|
38
208
|
end
|
39
209
|
|
40
|
-
|
41
|
-
|
210
|
+
# Sets elasticsearch <tt>size</tt> search request param
|
211
|
+
# Default value is set in the elasticsearch and is 10.
|
212
|
+
#
|
213
|
+
# UsersIndex.filter{ name == 'Johny' }.limit(100)
|
214
|
+
# # => {body: {
|
215
|
+
# query: {...},
|
216
|
+
# size: 100
|
217
|
+
# }}
|
218
|
+
#
|
219
|
+
def limit value
|
220
|
+
chain { criteria.update_options size: Integer(value) }
|
42
221
|
end
|
43
222
|
|
44
|
-
|
45
|
-
|
223
|
+
# Sets elasticsearch <tt>from</tt> search request param
|
224
|
+
#
|
225
|
+
# UsersIndex.filter{ name == 'Johny' }.offset(300)
|
226
|
+
# # => {body: {
|
227
|
+
# query: {...},
|
228
|
+
# from: 300
|
229
|
+
# }}
|
230
|
+
#
|
231
|
+
def offset value
|
232
|
+
chain { criteria.update_options from: Integer(value) }
|
46
233
|
end
|
47
234
|
|
48
|
-
|
235
|
+
# Adds facets section to the search request.
|
236
|
+
# All the chained facets a merged and added to the
|
237
|
+
# search request
|
238
|
+
#
|
239
|
+
# UsersIndex.facets(tags: {terms: {field: 'tags'}}).facets(ages: {terms: {field: 'age'}})
|
240
|
+
# # => {body: {
|
241
|
+
# query: {...},
|
242
|
+
# facets: {tags: {terms: {field: 'tags'}}, ages: {terms: {field: 'age'}}}
|
243
|
+
# }}
|
244
|
+
#
|
245
|
+
def facets params
|
49
246
|
chain { criteria.update_facets params }
|
50
247
|
end
|
51
248
|
|
52
|
-
|
249
|
+
# Adds one or more query to the search request
|
250
|
+
# Internally queries are stored as an array
|
251
|
+
# While the full query compilation this array compiles
|
252
|
+
# according to <tt>:query_mode</tt> option value
|
253
|
+
#
|
254
|
+
# By default it joines inside <tt>must</tt> query
|
255
|
+
# See <tt>#query_mode</tt> chainable method for more info.
|
256
|
+
#
|
257
|
+
# UsersIndex.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}})
|
258
|
+
# UsersIndex::User.query(text: {name: 'Johny'}).query(range: {age: {lte: 42}})
|
259
|
+
# # => {body: {
|
260
|
+
# query: {bool: {must: [{text: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}}
|
261
|
+
# }}
|
262
|
+
#
|
263
|
+
# If only one query was specified, it will become a result
|
264
|
+
# query as is, without joining.
|
265
|
+
#
|
266
|
+
# UsersIndex.query(text: {name: 'Johny'})
|
267
|
+
# # => {body: {
|
268
|
+
# query: {text: {name: 'Johny'}}
|
269
|
+
# }}
|
270
|
+
#
|
271
|
+
def query params
|
272
|
+
chain { criteria.update_queries params }
|
273
|
+
end
|
274
|
+
|
275
|
+
# Adds one or more filter to the search request
|
276
|
+
# Internally filters are stored as an array
|
277
|
+
# While the full query compilation this array compiles
|
278
|
+
# according to <tt>:filter_mode</tt> option value
|
279
|
+
#
|
280
|
+
# By default it joines inside <tt>and</tt> filter
|
281
|
+
# See <tt>#filter_mode</tt> chainable method for more info.
|
282
|
+
#
|
283
|
+
# Also this method supports block DSL.
|
284
|
+
# See <tt>Chewy::Query::Context</tt> for more info.
|
285
|
+
#
|
286
|
+
# UsersIndex.filter(term: {name: 'Johny'}).filter(range: {age: {lte: 42}})
|
287
|
+
# UsersIndex::User.filter(term: {name: 'Johny'}).filter(range: {age: {lte: 42}})
|
288
|
+
# UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }
|
289
|
+
# # => {body: {query: {filtered: {
|
290
|
+
# query: {...},
|
291
|
+
# filter: {and: [{term: {name: 'Johny'}}, {range: {age: {lte: 42}}}]}
|
292
|
+
# }}}}
|
293
|
+
#
|
294
|
+
# If only one filter was specified, it will become a result
|
295
|
+
# filter as is, without joining.
|
296
|
+
#
|
297
|
+
# UsersIndex.filter(term: {name: 'Johny'})
|
298
|
+
# # => {body: {query: {filtered: {
|
299
|
+
# query: {...},
|
300
|
+
# filter: {term: {name: 'Johny'}}
|
301
|
+
# }}}}
|
302
|
+
#
|
303
|
+
def filter params = nil, &block
|
304
|
+
params = Context.new(&block).__render__ if block
|
53
305
|
chain { criteria.update_filters params }
|
54
306
|
end
|
55
307
|
|
56
|
-
|
308
|
+
# Sets search request sorting
|
309
|
+
#
|
310
|
+
# UsersIndex.order(:first_name, :last_name).order(age: :desc).order(price: {order: :asc, mode: :avg})
|
311
|
+
# # => {body: {
|
312
|
+
# query: {...},
|
313
|
+
# sort: ['first_name', 'last_name', {age: 'desc'}, {price: {order: 'asc', mode: 'avg'}}]
|
314
|
+
# }}
|
315
|
+
#
|
316
|
+
def order *params
|
57
317
|
chain { criteria.update_sort params }
|
58
318
|
end
|
59
319
|
|
60
|
-
|
320
|
+
# Cleans up previous search sorting and sets the new one
|
321
|
+
#
|
322
|
+
# UsersIndex.order(:first_name, :last_name).order(age: :desc).reorder(price: {order: :asc, mode: :avg})
|
323
|
+
# # => {body: {
|
324
|
+
# query: {...},
|
325
|
+
# sort: [{price: {order: 'asc', mode: 'avg'}}]
|
326
|
+
# }}
|
327
|
+
#
|
328
|
+
def reorder *params
|
61
329
|
chain { criteria.update_sort params, purge: true }
|
62
330
|
end
|
63
331
|
|
64
|
-
|
332
|
+
# Sets search request field list
|
333
|
+
#
|
334
|
+
# UsersIndex.only(:first_name, :last_name).only(:age)
|
335
|
+
# # => {body: {
|
336
|
+
# query: {...},
|
337
|
+
# fields: ['first_name', 'last_name', 'age']
|
338
|
+
# }}
|
339
|
+
#
|
340
|
+
def only *params
|
65
341
|
chain { criteria.update_fields params }
|
66
342
|
end
|
67
343
|
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
344
|
+
# Cleans up previous search field list and sets the new one
|
345
|
+
#
|
346
|
+
# UsersIndex.only(:first_name, :last_name).only!(:age)
|
347
|
+
# # => {body: {
|
348
|
+
# query: {...},
|
349
|
+
# fields: ['age']
|
350
|
+
# }}
|
351
|
+
#
|
352
|
+
def only! *params
|
353
|
+
chain { criteria.update_fields params, purge: true }
|
73
354
|
end
|
74
355
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
356
|
+
# Specify types participating in the search result
|
357
|
+
# Works via <tt>types</tt> filter. Always merged with another filters
|
358
|
+
# with the <tt>and</tt> filter.
|
359
|
+
#
|
360
|
+
# UsersIndex.types(:admin, :manager).filters{ name == 'Johny' }.filters{ age <= 42 }
|
361
|
+
# # => {body: {query: {filtered: {
|
362
|
+
# query: {...},
|
363
|
+
# filter: {and: [
|
364
|
+
# {or: [
|
365
|
+
# {type: {value: 'admin'}},
|
366
|
+
# {type: {value: 'manager'}}
|
367
|
+
# ]},
|
368
|
+
# {term: {name: 'Johny'}},
|
369
|
+
# {range: {age: {lte: 42}}}
|
370
|
+
# ]}
|
371
|
+
# }}}}
|
372
|
+
#
|
373
|
+
# UsersIndex.types(:admin, :manager).filters{ name == 'Johny' }.filters{ age <= 42 }.filter_mode(:or)
|
374
|
+
# # => {body: {query: {filtered: {
|
375
|
+
# query: {...},
|
376
|
+
# filter: {and: [
|
377
|
+
# {or: [
|
378
|
+
# {type: {value: 'admin'}},
|
379
|
+
# {type: {value: 'manager'}}
|
380
|
+
# ]},
|
381
|
+
# {or: [
|
382
|
+
# {term: {name: 'Johny'}},
|
383
|
+
# {range: {age: {lte: 42}}}
|
384
|
+
# ]}
|
385
|
+
# ]}
|
386
|
+
# }}}}
|
387
|
+
#
|
388
|
+
def types *params
|
389
|
+
if params.any?
|
390
|
+
chain { criteria.update_types params }
|
391
|
+
else
|
392
|
+
@types
|
393
|
+
end
|
79
394
|
end
|
80
395
|
|
81
|
-
|
82
|
-
|
396
|
+
# Acts the same way as <tt>types</tt>, but cleans up previously set types
|
397
|
+
#
|
398
|
+
# UsersIndex.types(:admin).types!(:manager)
|
399
|
+
# # => {body: {query: {filtered: {
|
400
|
+
# query: {...},
|
401
|
+
# filter: {type: {value: 'manager'}}
|
402
|
+
# }}}}
|
403
|
+
#
|
404
|
+
def types! *params
|
405
|
+
chain { criteria.update_types params, purge: true }
|
83
406
|
end
|
84
407
|
|
85
|
-
|
86
|
-
|
408
|
+
# Merges two queries.
|
409
|
+
# Merges all the values in criteria with the same rules as values added manually.
|
410
|
+
#
|
411
|
+
# scope1 = UsersIndex.filter{ name == 'Johny' }
|
412
|
+
# scope2 = UsersIndex.filter{ age <= 42 }
|
413
|
+
# scope3 = UsersIndex.filter{ name == 'Johny' }.filter{ age <= 42 }
|
414
|
+
#
|
415
|
+
# scope1.merge(scope2) == scope3 # => true
|
416
|
+
#
|
417
|
+
def merge other
|
418
|
+
chain { criteria.merge!(other.criteria) }
|
87
419
|
end
|
88
420
|
|
89
|
-
|
90
|
-
if criteria.filters.many?
|
91
|
-
{and: criteria.filters}
|
92
|
-
else
|
93
|
-
criteria.filters.first
|
94
|
-
end
|
95
|
-
end
|
421
|
+
protected
|
96
422
|
|
97
|
-
def
|
98
|
-
|
99
|
-
|
100
|
-
filtered: {
|
101
|
-
query: criteria.query? ? criteria.query : {match_all: {}},
|
102
|
-
filter: _filters
|
103
|
-
}
|
104
|
-
}}
|
105
|
-
elsif criteria.query?
|
106
|
-
{query: criteria.query}
|
107
|
-
else
|
108
|
-
{}
|
109
|
-
end
|
423
|
+
def initialize_clone other
|
424
|
+
@criteria = other.criteria.clone
|
425
|
+
reset
|
110
426
|
end
|
111
427
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
body = body.merge!(fields: criteria.fields) if criteria.fields?
|
117
|
-
{body: body}
|
428
|
+
private
|
429
|
+
|
430
|
+
def chain &block
|
431
|
+
clone.tap { |q| q.instance_eval(&block) }
|
118
432
|
end
|
119
433
|
|
120
|
-
def
|
121
|
-
|
434
|
+
def reset
|
435
|
+
@_request, @_response, @_results = nil
|
122
436
|
end
|
123
437
|
|
124
438
|
def _request
|
125
|
-
|
439
|
+
@_request ||= criteria.request_body.merge(index: index.index_name, type: types)
|
126
440
|
end
|
127
441
|
|
128
442
|
def _response
|
129
|
-
@_response ||=
|
443
|
+
@_response ||= begin
|
444
|
+
ActiveSupport::Notifications.instrument 'search_query.chewy', request: _request, index: index do
|
445
|
+
index.client.search(_request)
|
446
|
+
end
|
447
|
+
end
|
130
448
|
end
|
131
449
|
|
132
450
|
def _results
|
@@ -134,7 +452,7 @@ module Chewy
|
|
134
452
|
attributes = hit['_source'] || hit['fields'] || {}
|
135
453
|
attributes.reverse_merge!(id: hit['_id']).merge!(_score: hit['_score'])
|
136
454
|
attributes.merge!(_explain: hit['_explanation']) if hit['_explanation']
|
137
|
-
index.
|
455
|
+
index.type_hash[hit['_type']].new attributes
|
138
456
|
end
|
139
457
|
end
|
140
458
|
end
|