chewy 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.rvmrc +1 -0
- data/.travis.yml +7 -0
- data/Gemfile +12 -0
- data/Guardfile +24 -0
- data/LICENSE.txt +22 -0
- data/README.md +208 -0
- data/Rakefile +6 -0
- data/chewy.gemspec +32 -0
- data/lib/chewy.rb +55 -0
- data/lib/chewy/config.rb +48 -0
- data/lib/chewy/fields/base.rb +49 -0
- data/lib/chewy/fields/default.rb +10 -0
- data/lib/chewy/fields/root.rb +10 -0
- data/lib/chewy/index.rb +71 -0
- data/lib/chewy/index/actions.rb +43 -0
- data/lib/chewy/index/client.rb +13 -0
- data/lib/chewy/index/search.rb +26 -0
- data/lib/chewy/query.rb +141 -0
- data/lib/chewy/query/criteria.rb +81 -0
- data/lib/chewy/query/loading.rb +27 -0
- data/lib/chewy/query/pagination.rb +39 -0
- data/lib/chewy/rspec.rb +1 -0
- data/lib/chewy/rspec/update_index.rb +121 -0
- data/lib/chewy/type.rb +22 -0
- data/lib/chewy/type/adapter/active_record.rb +27 -0
- data/lib/chewy/type/adapter/object.rb +22 -0
- data/lib/chewy/type/base.rb +41 -0
- data/lib/chewy/type/import.rb +67 -0
- data/lib/chewy/type/mapping.rb +50 -0
- data/lib/chewy/type/observe.rb +37 -0
- data/lib/chewy/type/wrapper.rb +35 -0
- data/lib/chewy/version.rb +3 -0
- data/spec/chewy/config_spec.rb +50 -0
- data/spec/chewy/fields/base_spec.rb +70 -0
- data/spec/chewy/fields/default_spec.rb +6 -0
- data/spec/chewy/fields/root_spec.rb +6 -0
- data/spec/chewy/index/actions_spec.rb +53 -0
- data/spec/chewy/index/client_spec.rb +18 -0
- data/spec/chewy/index/search_spec.rb +54 -0
- data/spec/chewy/index_spec.rb +65 -0
- data/spec/chewy/query/criteria_spec.rb +73 -0
- data/spec/chewy/query/loading_spec.rb +37 -0
- data/spec/chewy/query/pagination_spec.rb +40 -0
- data/spec/chewy/query_spec.rb +110 -0
- data/spec/chewy/rspec/update_index_spec.rb +149 -0
- data/spec/chewy/type/import_spec.rb +68 -0
- data/spec/chewy/type/mapping_spec.rb +54 -0
- data/spec/chewy/type/observe_spec.rb +55 -0
- data/spec/chewy/type/wrapper_spec.rb +35 -0
- data/spec/chewy/type_spec.rb +43 -0
- data/spec/chewy_spec.rb +36 -0
- data/spec/spec_helper.rb +48 -0
- data/spec/support/class_helpers.rb +16 -0
- data/spec/support/fail_helpers.rb +13 -0
- metadata +249 -0
@@ -0,0 +1,49 @@
|
|
1
|
+
module Chewy
|
2
|
+
module Fields
|
3
|
+
class Base
|
4
|
+
attr_reader :name, :options, :value
|
5
|
+
|
6
|
+
def initialize(name, options = {})
|
7
|
+
@name, @options, @nested = name.to_sym, options, {}
|
8
|
+
@value = @options.delete(:value)
|
9
|
+
end
|
10
|
+
|
11
|
+
def multi_field?
|
12
|
+
@options[:type] == 'multi_field'
|
13
|
+
end
|
14
|
+
|
15
|
+
def compose(object)
|
16
|
+
result = value ? value.call(object) : object.send(name)
|
17
|
+
|
18
|
+
result = if result.is_a?(Enumerable)
|
19
|
+
result.map { |object| nested_compose(object) }
|
20
|
+
else
|
21
|
+
nested_compose(result)
|
22
|
+
end if nested.any? && !multi_field?
|
23
|
+
|
24
|
+
{name => result.as_json}
|
25
|
+
end
|
26
|
+
|
27
|
+
def nested(field = nil)
|
28
|
+
if field
|
29
|
+
@nested[field.name] = field
|
30
|
+
else
|
31
|
+
@nested
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def mappings_hash
|
36
|
+
subfields = nested.any? ? {
|
37
|
+
(multi_field? ? :fields : :properties) => nested.values.map(&:mappings_hash).inject(:merge)
|
38
|
+
} : {}
|
39
|
+
{name => options.merge(subfields)}
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
def nested_compose(value)
|
45
|
+
nested.values.map { |field| field.compose(value) }.inject(:merge)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
data/lib/chewy/index.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'chewy/index/actions'
|
2
|
+
require 'chewy/index/client'
|
3
|
+
require 'chewy/index/search'
|
4
|
+
|
5
|
+
module Chewy
|
6
|
+
class Index
|
7
|
+
include Actions
|
8
|
+
include Client
|
9
|
+
include Search
|
10
|
+
|
11
|
+
class_attribute :types
|
12
|
+
self.types = {}
|
13
|
+
|
14
|
+
class_attribute :_settings
|
15
|
+
self._settings = {}
|
16
|
+
|
17
|
+
def self.define_type(name_or_scope, &block)
|
18
|
+
type_class = Chewy::Type.new(self, name_or_scope, &block)
|
19
|
+
self.types = types.merge(type_class.type_name => type_class)
|
20
|
+
|
21
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
22
|
+
def self.#{type_class.type_name}
|
23
|
+
types['#{type_class.type_name}']
|
24
|
+
end
|
25
|
+
RUBY
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.settings(params)
|
29
|
+
self._settings = params
|
30
|
+
end
|
31
|
+
|
32
|
+
def self.index_name(suggest = nil)
|
33
|
+
if suggest
|
34
|
+
@index_name = suggest.to_s
|
35
|
+
else
|
36
|
+
@index_name ||= (name.gsub(/Index\Z/, '').demodulize.underscore if name)
|
37
|
+
end
|
38
|
+
@index_name or raise UndefinedIndex
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.settings_hash
|
42
|
+
_settings.present? ? {settings: _settings} : {}
|
43
|
+
end
|
44
|
+
|
45
|
+
def self.mappings_hash
|
46
|
+
mappings = types.values.map(&:mappings_hash).inject(:merge)
|
47
|
+
mappings.present? ? {mappings: mappings} : {}
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.index_params
|
51
|
+
[settings_hash, mappings_hash].inject(:merge)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.search_index
|
55
|
+
self
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.search_type
|
59
|
+
types.keys
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.import
|
63
|
+
types.values.all? { |t| t.import }
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.reset
|
67
|
+
index_purge!
|
68
|
+
import
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Chewy
|
2
|
+
class Index
|
3
|
+
module Actions
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def index_exists?
|
8
|
+
client.indices.exists(index: index_name)
|
9
|
+
end
|
10
|
+
|
11
|
+
def index_create
|
12
|
+
index_create!
|
13
|
+
rescue Elasticsearch::Transport::Transport::Errors::BadRequest
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
def index_create!
|
18
|
+
client.indices.create(index: index_name, body: index_params)
|
19
|
+
end
|
20
|
+
|
21
|
+
def index_delete
|
22
|
+
index_delete!
|
23
|
+
rescue Elasticsearch::Transport::Transport::Errors::NotFound
|
24
|
+
false
|
25
|
+
end
|
26
|
+
|
27
|
+
def index_delete!
|
28
|
+
client.indices.delete(index: index_name)
|
29
|
+
end
|
30
|
+
|
31
|
+
def index_purge
|
32
|
+
index_delete
|
33
|
+
index_create
|
34
|
+
end
|
35
|
+
|
36
|
+
def index_purge!
|
37
|
+
index_delete
|
38
|
+
index_create!
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Chewy
|
2
|
+
class Index
|
3
|
+
module Search
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def search
|
8
|
+
Chewy::Query.new(search_index, type: search_type)
|
9
|
+
end
|
10
|
+
|
11
|
+
def search_string query, options = {}
|
12
|
+
options = options.merge(index: search_index.index_name, type: search_type, q: query)
|
13
|
+
client.search(options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def search_index
|
17
|
+
raise NotImplementedError
|
18
|
+
end
|
19
|
+
|
20
|
+
def search_type
|
21
|
+
raise NotImplementedError
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/chewy/query.rb
ADDED
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'chewy/query/criteria'
|
2
|
+
require 'chewy/query/loading'
|
3
|
+
require 'chewy/query/pagination'
|
4
|
+
|
5
|
+
module Chewy
|
6
|
+
class Query
|
7
|
+
include Enumerable
|
8
|
+
include Loading
|
9
|
+
include Pagination
|
10
|
+
|
11
|
+
DEFAULT_OPTIONS = {}
|
12
|
+
|
13
|
+
delegate :each, to: :_results
|
14
|
+
alias_method :to_ary, :to_a
|
15
|
+
|
16
|
+
attr_reader :index, :options, :criteria
|
17
|
+
|
18
|
+
def initialize(index, options = {})
|
19
|
+
@index, @options = index, DEFAULT_OPTIONS.merge(options)
|
20
|
+
@criteria = Criteria.new
|
21
|
+
reset
|
22
|
+
end
|
23
|
+
|
24
|
+
def ==(other)
|
25
|
+
if other.is_a?(self.class)
|
26
|
+
other.criteria == criteria
|
27
|
+
else
|
28
|
+
to_a == other
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def explain(value = nil)
|
33
|
+
chain { criteria.update_search explain: (value.nil? ? true : value) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def limit(value)
|
37
|
+
chain { criteria.update_search size: Integer(value) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def offset(value)
|
41
|
+
chain { criteria.update_search from: Integer(value) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def query(params)
|
45
|
+
chain { criteria.update_query params }
|
46
|
+
end
|
47
|
+
|
48
|
+
def facets(params)
|
49
|
+
chain { criteria.update_facets params }
|
50
|
+
end
|
51
|
+
|
52
|
+
def filter(params)
|
53
|
+
chain { criteria.update_filters params }
|
54
|
+
end
|
55
|
+
|
56
|
+
def order(*params)
|
57
|
+
chain { criteria.update_sort params }
|
58
|
+
end
|
59
|
+
|
60
|
+
def reorder(*params)
|
61
|
+
chain { criteria.update_sort params, purge: true }
|
62
|
+
end
|
63
|
+
|
64
|
+
def only(*params)
|
65
|
+
chain { criteria.update_fields params }
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
def initialize_clone(other)
|
71
|
+
@criteria = other.criteria.clone
|
72
|
+
reset
|
73
|
+
end
|
74
|
+
|
75
|
+
private
|
76
|
+
|
77
|
+
def chain &block
|
78
|
+
clone.tap { |q| q.instance_eval(&block) }
|
79
|
+
end
|
80
|
+
|
81
|
+
def reset
|
82
|
+
@_response, @_results = nil
|
83
|
+
end
|
84
|
+
|
85
|
+
def types
|
86
|
+
@types ||= Array.wrap(options[:type] || options[:types])
|
87
|
+
end
|
88
|
+
|
89
|
+
def _filters
|
90
|
+
if criteria.filters.many?
|
91
|
+
{and: criteria.filters}
|
92
|
+
else
|
93
|
+
criteria.filters.first
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def _request_query
|
98
|
+
if criteria.filters?
|
99
|
+
{query: {
|
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
|
110
|
+
end
|
111
|
+
|
112
|
+
def _request_body
|
113
|
+
body = _request_query
|
114
|
+
body = body.merge!(facets: criteria.facets) if criteria.facets?
|
115
|
+
body = body.merge!(sort: criteria.sort) if criteria.sort?
|
116
|
+
body = body.merge!(fields: criteria.fields) if criteria.fields?
|
117
|
+
{body: body}
|
118
|
+
end
|
119
|
+
|
120
|
+
def _request_target
|
121
|
+
{index: index.index_name, type: types}
|
122
|
+
end
|
123
|
+
|
124
|
+
def _request
|
125
|
+
[criteria.search, _request_target, _request_body].inject(:merge)
|
126
|
+
end
|
127
|
+
|
128
|
+
def _response
|
129
|
+
@_response ||= index.client.search(_request)
|
130
|
+
end
|
131
|
+
|
132
|
+
def _results
|
133
|
+
@_results ||= _response['hits']['hits'].map do |hit|
|
134
|
+
attributes = hit['_source'] || hit['fields'] || {}
|
135
|
+
attributes.reverse_merge!(id: hit['_id']).merge!(_score: hit['_score'])
|
136
|
+
attributes.merge!(_explain: hit['_explanation']) if hit['_explanation']
|
137
|
+
index.types[hit['_type']].new attributes
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module Chewy
|
2
|
+
class Query
|
3
|
+
class Criteria
|
4
|
+
STORAGES = [:search, :query, :facets, :filters, :sort, :fields]
|
5
|
+
|
6
|
+
def ==(other)
|
7
|
+
storages == other.storages
|
8
|
+
end
|
9
|
+
|
10
|
+
def storages
|
11
|
+
STORAGES.map { |storage| send(storage) }
|
12
|
+
end
|
13
|
+
|
14
|
+
[:search, :query, :facets].each do |storage|
|
15
|
+
class_eval <<-METHODS, __FILE__, __LINE__ + 1
|
16
|
+
def #{storage}
|
17
|
+
@#{storage} ||= {}
|
18
|
+
end
|
19
|
+
METHODS
|
20
|
+
end
|
21
|
+
|
22
|
+
[:filters, :sort, :fields].each do |storage|
|
23
|
+
class_eval <<-METHODS, __FILE__, __LINE__ + 1
|
24
|
+
def #{storage}
|
25
|
+
@#{storage} ||= []
|
26
|
+
end
|
27
|
+
METHODS
|
28
|
+
end
|
29
|
+
|
30
|
+
STORAGES.each do |storage|
|
31
|
+
class_eval <<-METHODS, __FILE__, __LINE__ + 1
|
32
|
+
def #{storage}?
|
33
|
+
#{storage}.any?
|
34
|
+
end
|
35
|
+
METHODS
|
36
|
+
end
|
37
|
+
|
38
|
+
def update_search(modifer)
|
39
|
+
search.merge!(modifer)
|
40
|
+
end
|
41
|
+
|
42
|
+
def update_query(modifer)
|
43
|
+
query.merge!(modifer)
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def update_facets(modifer)
|
48
|
+
facets.merge!(modifer)
|
49
|
+
end
|
50
|
+
|
51
|
+
def update_filters(modifer)
|
52
|
+
@filters = filters + Array.wrap(modifer).delete_if(&:blank?)
|
53
|
+
end
|
54
|
+
|
55
|
+
def update_sort(modifer, options = {})
|
56
|
+
@sort = nil if options[:purge]
|
57
|
+
modifer = Array.wrap(modifer).flatten.map do |element|
|
58
|
+
element.is_a?(Hash) ? element.map { |k, v| {k => v} } : element
|
59
|
+
end.flatten
|
60
|
+
@sort = sort + modifer
|
61
|
+
end
|
62
|
+
|
63
|
+
def update_fields(modifer, options = {})
|
64
|
+
@fields = nil if options[:purge]
|
65
|
+
@fields = (fields + Array.wrap(modifer).flatten.map(&:to_s).delete_if(&:blank?)).uniq
|
66
|
+
end
|
67
|
+
|
68
|
+
protected
|
69
|
+
|
70
|
+
def initialize_clone(other)
|
71
|
+
STORAGES.each do |storage|
|
72
|
+
value = other.send(storage)
|
73
|
+
if value
|
74
|
+
value = Marshal.load(Marshal.dump(value))
|
75
|
+
instance_variable_set("@#{storage}", value)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|