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.
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