chewy 0.0.1
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 +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
|