chewy 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +3 -0
  4. data/.rvmrc +1 -0
  5. data/.travis.yml +7 -0
  6. data/Gemfile +12 -0
  7. data/Guardfile +24 -0
  8. data/LICENSE.txt +22 -0
  9. data/README.md +208 -0
  10. data/Rakefile +6 -0
  11. data/chewy.gemspec +32 -0
  12. data/lib/chewy.rb +55 -0
  13. data/lib/chewy/config.rb +48 -0
  14. data/lib/chewy/fields/base.rb +49 -0
  15. data/lib/chewy/fields/default.rb +10 -0
  16. data/lib/chewy/fields/root.rb +10 -0
  17. data/lib/chewy/index.rb +71 -0
  18. data/lib/chewy/index/actions.rb +43 -0
  19. data/lib/chewy/index/client.rb +13 -0
  20. data/lib/chewy/index/search.rb +26 -0
  21. data/lib/chewy/query.rb +141 -0
  22. data/lib/chewy/query/criteria.rb +81 -0
  23. data/lib/chewy/query/loading.rb +27 -0
  24. data/lib/chewy/query/pagination.rb +39 -0
  25. data/lib/chewy/rspec.rb +1 -0
  26. data/lib/chewy/rspec/update_index.rb +121 -0
  27. data/lib/chewy/type.rb +22 -0
  28. data/lib/chewy/type/adapter/active_record.rb +27 -0
  29. data/lib/chewy/type/adapter/object.rb +22 -0
  30. data/lib/chewy/type/base.rb +41 -0
  31. data/lib/chewy/type/import.rb +67 -0
  32. data/lib/chewy/type/mapping.rb +50 -0
  33. data/lib/chewy/type/observe.rb +37 -0
  34. data/lib/chewy/type/wrapper.rb +35 -0
  35. data/lib/chewy/version.rb +3 -0
  36. data/spec/chewy/config_spec.rb +50 -0
  37. data/spec/chewy/fields/base_spec.rb +70 -0
  38. data/spec/chewy/fields/default_spec.rb +6 -0
  39. data/spec/chewy/fields/root_spec.rb +6 -0
  40. data/spec/chewy/index/actions_spec.rb +53 -0
  41. data/spec/chewy/index/client_spec.rb +18 -0
  42. data/spec/chewy/index/search_spec.rb +54 -0
  43. data/spec/chewy/index_spec.rb +65 -0
  44. data/spec/chewy/query/criteria_spec.rb +73 -0
  45. data/spec/chewy/query/loading_spec.rb +37 -0
  46. data/spec/chewy/query/pagination_spec.rb +40 -0
  47. data/spec/chewy/query_spec.rb +110 -0
  48. data/spec/chewy/rspec/update_index_spec.rb +149 -0
  49. data/spec/chewy/type/import_spec.rb +68 -0
  50. data/spec/chewy/type/mapping_spec.rb +54 -0
  51. data/spec/chewy/type/observe_spec.rb +55 -0
  52. data/spec/chewy/type/wrapper_spec.rb +35 -0
  53. data/spec/chewy/type_spec.rb +43 -0
  54. data/spec/chewy_spec.rb +36 -0
  55. data/spec/spec_helper.rb +48 -0
  56. data/spec/support/class_helpers.rb +16 -0
  57. data/spec/support/fail_helpers.rb +13 -0
  58. 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
@@ -0,0 +1,10 @@
1
+ module Chewy
2
+ module Fields
3
+ class Default < Chewy::Fields::Base
4
+ def initialize(name, options = {})
5
+ options.reverse_merge!(type: 'string')
6
+ super(name, options)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ module Chewy
2
+ module Fields
3
+ class Root < Chewy::Fields::Base
4
+ def initialize(name, options = {})
5
+ options.reverse_merge!(value: ->(_){_})
6
+ super(name, options)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -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,13 @@
1
+ module Chewy
2
+ class Index
3
+ module Client
4
+ extend ActiveSupport::Concern
5
+
6
+ module ClassMethods
7
+ def client
8
+ Thread.current[:chewy_client] ||= ::Elasticsearch::Client.new Chewy.client_options
9
+ end
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -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