esse 0.2.0 → 0.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/lib/esse/backend/index/aliases.rb +8 -8
  3. data/lib/esse/backend/index/close.rb +3 -3
  4. data/lib/esse/backend/index/create.rb +4 -4
  5. data/lib/esse/backend/index/delete.rb +4 -4
  6. data/lib/esse/backend/index/documents.rb +253 -6
  7. data/lib/esse/backend/index/existance.rb +3 -3
  8. data/lib/esse/backend/index/open.rb +3 -3
  9. data/lib/esse/backend/index/refresh.rb +7 -5
  10. data/lib/esse/backend/index/reset.rb +4 -4
  11. data/lib/esse/backend/index/update.rb +7 -7
  12. data/lib/esse/backend/index.rb +16 -14
  13. data/lib/esse/backend/repository_backend.rb +105 -0
  14. data/lib/esse/cli/event_listener.rb +14 -0
  15. data/lib/esse/cli/generate.rb +53 -12
  16. data/lib/esse/cli/index/base_operation.rb +5 -13
  17. data/lib/esse/cli/index/import.rb +6 -2
  18. data/lib/esse/cli/index/update_mapping.rb +3 -4
  19. data/lib/esse/cli/index.rb +2 -0
  20. data/lib/esse/cli/templates/{type_collection.rb.erb → collection.rb.erb} +6 -18
  21. data/lib/esse/cli/templates/config.rb.erb +13 -3
  22. data/lib/esse/cli/templates/index.rb.erb +53 -109
  23. data/lib/esse/cli/templates/mappings.json +27 -0
  24. data/lib/esse/cli/templates/serializer.rb.erb +34 -0
  25. data/lib/esse/cli/templates/settings.json +62 -0
  26. data/lib/esse/client_proxy/search.rb +44 -0
  27. data/lib/esse/client_proxy.rb +32 -0
  28. data/lib/esse/cluster.rb +64 -9
  29. data/lib/esse/cluster_engine.rb +42 -0
  30. data/lib/esse/collection.rb +18 -0
  31. data/lib/esse/config.rb +14 -2
  32. data/lib/esse/core.rb +23 -6
  33. data/lib/esse/deprecations/cluster.rb +27 -0
  34. data/lib/esse/deprecations/index.rb +19 -0
  35. data/lib/esse/deprecations/repository.rb +19 -0
  36. data/lib/esse/deprecations.rb +3 -0
  37. data/lib/esse/dynamic_template.rb +39 -0
  38. data/lib/esse/errors.rb +53 -2
  39. data/lib/esse/events/event.rb +4 -19
  40. data/lib/esse/events.rb +3 -0
  41. data/lib/esse/hash_document.rb +38 -0
  42. data/lib/esse/import/bulk.rb +96 -0
  43. data/lib/esse/import/request_body.rb +60 -0
  44. data/lib/esse/index/attributes.rb +98 -0
  45. data/lib/esse/index/base.rb +1 -1
  46. data/lib/esse/index/inheritance.rb +30 -0
  47. data/lib/esse/index/mappings.rb +6 -19
  48. data/lib/esse/index/object_document_mapper.rb +95 -0
  49. data/lib/esse/index/plugins.rb +42 -0
  50. data/lib/esse/index/search.rb +27 -0
  51. data/lib/esse/index/settings.rb +2 -2
  52. data/lib/esse/index/type.rb +52 -11
  53. data/lib/esse/index.rb +10 -6
  54. data/lib/esse/index_mapping.rb +10 -2
  55. data/lib/esse/index_setting.rb +3 -1
  56. data/lib/esse/null_document.rb +35 -0
  57. data/lib/esse/plugins.rb +12 -0
  58. data/lib/esse/primitives/hstring.rb +1 -1
  59. data/lib/esse/{index_type → repository}/actions.rb +1 -1
  60. data/lib/esse/{index_type → repository}/backend.rb +2 -2
  61. data/lib/esse/repository/object_document_mapper.rb +157 -0
  62. data/lib/esse/repository.rb +18 -0
  63. data/lib/esse/search/query.rb +105 -0
  64. data/lib/esse/search/response.rb +46 -0
  65. data/lib/esse/serializer.rb +76 -0
  66. data/lib/esse/version.rb +1 -1
  67. data/lib/esse.rb +20 -5
  68. metadata +35 -30
  69. data/lib/esse/backend/index_type/documents.rb +0 -214
  70. data/lib/esse/backend/index_type.rb +0 -37
  71. data/lib/esse/cli/templates/type_mappings.json +0 -6
  72. data/lib/esse/cli/templates/type_serializer.rb.erb +0 -23
  73. data/lib/esse/index/naming.rb +0 -64
  74. data/lib/esse/index_type/mappings.rb +0 -42
  75. data/lib/esse/index_type.rb +0 -15
  76. data/lib/esse/object_document_mapper.rb +0 -110
@@ -1,214 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Esse
4
- module Backend
5
- class IndexType
6
- module InstanceMethods
7
- # Resolve collection and index data
8
- #
9
- # @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
10
- # @option [String, nil] :suffix The index suffix. Defaults to the nil.
11
- # @option [Hash] :context The collection context. This value will be passed as argument to the collection
12
- # May be SQL condition or any other filter you have defined on the collection.
13
- def import(context: {}, suffix: nil, **options)
14
- each_serialized_batch(**(context || {})) do |batch|
15
- bulk(index: batch, suffix: suffix, **options)
16
- end
17
- end
18
- alias_method :import!, :import
19
-
20
- # Performs multiple indexing or delete operations in a single API call.
21
- # This reduces overhead and can greatly increase indexing speed.
22
- #
23
- # @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
24
- # @option [String, nil] :suffix The index suffix. Defaults to the nil.
25
- # @option [Array] :index list of serialized documents to be indexed(Optional)
26
- # @option [Array] :delete list of serialized documents to be deleted(Optional)
27
- # @option [Array] :create list of serialized documents to be created(Optional)
28
- # @return [Hash, nil] the elasticsearch response or nil if there is no data.
29
- #
30
- # @see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docs-bulk.html
31
- def bulk(index: nil, delete: nil, create: nil, suffix: nil, **options)
32
- body = []
33
- Array(index).each do |entry|
34
- id, data = Esse.doc_id!(entry)
35
- body << { index: { _id: id, data: data } } if id
36
- end
37
- Array(create).each do |entry|
38
- id, data = Esse.doc_id!(entry)
39
- body << { create: { _id: id, data: data } } if id
40
- end
41
- Array(delete).each do |entry|
42
- id, _data = Esse.doc_id!(entry, delete: [], keep: %w[_id id])
43
- body << { delete: { _id: id } } if id
44
- end
45
-
46
- return if body.empty?
47
-
48
- definition = {
49
- index: index_name(suffix: suffix),
50
- type: type_name,
51
- body: body,
52
- }.merge(options)
53
-
54
- client.bulk(definition)
55
- end
56
- alias_method :bulk!, :bulk
57
-
58
- # Adds a JSON document to the specified index and makes it searchable. If the document
59
- # already exists, updates the document and increments its version.
60
- #
61
- # UsersIndex::User.index(id: 1, body: { name: 'name' }) # { '_id' => 1, ...}
62
- #
63
- # @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
64
- # @option [String, Integer] :id The `_id` of the elasticsearch document
65
- # @option [Hash] :body The JSON document that will be indexed (Required)
66
- # @option [String, nil] :suffix The index suffix. Defaults to the nil.
67
- # @return [Hash] the elasticsearch response Hash
68
- #
69
- # @see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docs-index_.html
70
- def index(id:, body:, suffix: nil, **options)
71
- client.index(
72
- index: index_name(suffix: suffix), type: type_name, id: id, body: body, **options
73
- )
74
- end
75
- alias_method :index!, :index
76
-
77
- # Updates a document using the specified script.
78
- #
79
- # UsersIndex::User.update!(id: 1, body: { doc: { ... } }) # { '_id' => 1, ...}
80
- #
81
- # @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
82
- # @option [String, Integer] :id The `_id` of the elasticsearch document
83
- # @option [Hash] :body the body of the request
84
- # @option [String, nil] :suffix The index suffix. Defaults to the nil.
85
- # @raise [Elasticsearch::Transport::Transport::Errors::NotFound] when the doc does not exist
86
- # @return [Hash] elasticsearch response hash
87
- #
88
- # @see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docs-update.html
89
- def update!(id:, body:, suffix: nil, **options)
90
- client.update(
91
- index: index_name(suffix: suffix), type: type_name, id: id, body: body, **options
92
- )
93
- end
94
-
95
- # Updates a document using the specified script.
96
- #
97
- # UsersIndex::User.update(id: 1, body: { doc: { ... } }) # { '_id' => 1, ...}
98
- #
99
- # @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
100
- # @option [String, Integer] :id The `_id` of the elasticsearch document
101
- # @option [Hash] :body the body of the request
102
- # @option [String, nil] :suffix The index suffix. Defaults to the nil.
103
- # @return [Hash] the elasticsearch response, or an hash with 'errors' as true in case of failure
104
- #
105
- # @see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docs-update.html
106
- def update(id:, body:, suffix: nil, **options)
107
- update!(id: id, body: body, suffix: suffix, **options)
108
- rescue Elasticsearch::Transport::Transport::Errors::NotFound
109
- { 'errors' => true }
110
- end
111
-
112
- # Removes a JSON document from the specified index.
113
- #
114
- # UsersIndex::User.delete!(id: 1) # true
115
- # UsersIndex::User.delete!(id: 'missing') # raise Elasticsearch::Transport::Transport::Errors::NotFound
116
- #
117
- # @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
118
- # @option [String, Integer] :id The `_id` of the elasticsearch document
119
- # @option [String, nil] :suffix The index suffix. Defaults to the nil.
120
- # @raise [Elasticsearch::Transport::Transport::Errors::NotFound] when the doc does not exist
121
- # @return [Boolean] true when the operation is successfully completed
122
- #
123
- # @see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docs-delete.html
124
- def delete!(id:, suffix: nil, **options)
125
- client.delete(options.merge(index: index_name(suffix: suffix), type: type_name, id: id))
126
- end
127
-
128
- # Removes a JSON document from the specified index.
129
- #
130
- # UsersIndex::User.delete(id: 1) # true
131
- # UsersIndex::User.delete(id: 'missing') # false
132
- #
133
- # @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
134
- # @option [String, Integer] :id The `_id` of the elasticsearch document
135
- # @option [String, nil] :suffix The index suffix. Defaults to the nil.
136
- # @raise [Elasticsearch::Transport::Transport::Errors::NotFound] when the doc does not exist
137
- # @return [Boolean] true when the operation is successfully completed
138
- #
139
- # @see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docs-delete.html
140
- def delete(id:, suffix: nil, **options)
141
- delete!(id: id, suffix: suffix, **options)
142
- rescue Elasticsearch::Transport::Transport::Errors::NotFound
143
- false
144
- end
145
-
146
- # Gets the number of matches for a search query.
147
- #
148
- # UsersIndex::User.count # 999
149
- # UsersIndex::User.count(body: { ... }) # 32
150
- #
151
- # @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
152
- # @option [Hash] :body A query to restrict the results specified with the Query DSL (optional)
153
- # @option [String, nil] :suffix The index suffix. Defaults to the nil.
154
- # @return [Integer] amount of documents found
155
- #
156
- # @see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/search-count.html
157
- def count(suffix: nil, **options)
158
- response = client.count(options.merge(index: index_name(suffix: suffix), type: type_name))
159
- response['count']
160
- rescue Elasticsearch::Transport::Transport::Errors::NotFound
161
- 0
162
- end
163
-
164
- # Check if a JSON document exists
165
- #
166
- # UsersIndex::User.exist?(id: 1) # true
167
- # UsersIndex::User.exist?(id: 'missing') # false
168
- #
169
- # @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
170
- # @option [String, Integer] :id The `_id` of the elasticsearch document
171
- # @option [String, nil] :suffix The index suffix. Defaults to the nil.
172
- # @return [Boolean] true if the document exists
173
- def exist?(id:, suffix: nil, **options)
174
- client.exists(options.merge(index: index_name(suffix: suffix), type: type_name, id: id))
175
- end
176
-
177
- # Retrieves the specified JSON document from an index.
178
- #
179
- # UsersIndex::User.find!(id: 1) # { '_id' => 1, ... }
180
- # UsersIndex::User.find!(id: 'missing') # raise Elasticsearch::Transport::Transport::Errors::NotFound
181
- #
182
- # @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
183
- # @option [String, Integer] :id The `_id` of the elasticsearch document
184
- # @option [String, nil] :suffix The index suffix. Defaults to the nil.
185
- # @raise [Elasticsearch::Transport::Transport::Errors::NotFound] when the doc does not exist
186
- # @return [Hash] The elasticsearch document.
187
- #
188
- # @see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docs-get.html
189
- def find!(id:, suffix: nil, **options)
190
- client.get(options.merge(index: index_name(suffix: suffix), type: type_name, id: id))
191
- end
192
-
193
- # Retrieves the specified JSON document from an index.
194
- #
195
- # UsersIndex::User.find(id: 1) # { '_id' => 1, ... }
196
- # UsersIndex::User.find(id: 'missing') # nil
197
- #
198
- # @param options [Hash] Hash of paramenters that will be passed along to elasticsearch request
199
- # @option [String, Integer] :id The `_id` of the elasticsearch document
200
- # @option [String, nil] :suffix The index suffix. Defaults to the nil.
201
- # @return [Hash, nil] The elasticsearch document
202
- #
203
- # @see https://www.elastic.co/guide/en/elasticsearch/reference/7.5/docs-get.html
204
- def find(id:, suffix: nil, **options)
205
- find!(id: id, suffix: suffix, **options)
206
- rescue Elasticsearch::Transport::Transport::Errors::NotFound
207
- nil
208
- end
209
- end
210
-
211
- include InstanceMethods
212
- end
213
- end
214
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'forwardable'
4
-
5
- module Esse
6
- module Backend
7
- class IndexType
8
- require_relative 'index_type/documents'
9
-
10
- extend Forwardable
11
-
12
- # Type delegators
13
- def_delegators :@index_type, :type_name, :each_serialized_batch, :serialize
14
-
15
- def initialize(type)
16
- @index_type = type
17
- end
18
-
19
- protected
20
-
21
- def index_name(suffix: nil)
22
- suffix = Hstring.new(suffix).underscore.presence
23
- return index_class.index_name unless suffix
24
-
25
- [index_class.index_name, suffix].join('_')
26
- end
27
-
28
- def index_class
29
- @index_type.index
30
- end
31
-
32
- def client
33
- index_class.cluster.client
34
- end
35
- end
36
- end
37
- end
@@ -1,6 +0,0 @@
1
- {
2
- "name": {
3
- "type": "string",
4
- "index": "analyzed"
5
- }
6
- }
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class <%= @index_name %> < <%= @base_class %>
4
- module Serializers
5
- class <%= @type.camelize %>Serializer
6
- def initialize(<%= @type %>, **params)
7
- @entity = <%= @type %>
8
- @params = params
9
- end
10
-
11
- def to_h
12
- {
13
- id: entity.id, # This field is required
14
- name: entity.name,
15
- }
16
- end
17
-
18
- protected
19
-
20
- attr_reader :entity, :params
21
- end
22
- end
23
- end
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Esse
4
- class Index
5
- module ClassMethods
6
- TEMPLATE_DIRS = [
7
- '%<dirname>s/templates',
8
- '%<dirname>s'
9
- ].freeze
10
-
11
- def index_name=(value)
12
- @index_name = index_prefixed_name(value)
13
- end
14
-
15
- def index_name
16
- @index_name || index_prefixed_name(normalized_name)
17
- end
18
-
19
- def index_name?
20
- !index_name.nil?
21
- end
22
-
23
- def index_version=(value)
24
- @index_version = Hstring.new(value.to_s).underscore.presence
25
- end
26
-
27
- def index_version
28
- @index_version
29
- end
30
-
31
- def uname
32
- Hstring.new(name).underscore.presence
33
- end
34
-
35
- def index_directory
36
- return unless uname
37
- return if uname == 'Esse::Index'
38
-
39
- Esse.config.indices_directory.join(uname).to_s
40
- end
41
-
42
- def template_dirs
43
- return [] unless index_directory
44
-
45
- TEMPLATE_DIRS.map { |term| format(term, dirname: index_directory) }
46
- end
47
-
48
- protected
49
-
50
- def index_prefixed_name(value)
51
- return if value == '' || value.nil?
52
- return value.to_s unless cluster.index_prefix
53
-
54
- [cluster.index_prefix, value].join('_')
55
- end
56
-
57
- def normalized_name
58
- Hstring.new(name).demodulize.underscore.sub(/_(index)$/, '')
59
- end
60
- end
61
-
62
- extend ClassMethods
63
- end
64
- end
@@ -1,42 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Esse
4
- class IndexType
5
- # https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-put-mapping.html
6
- module ClassMethods
7
- # This method is only used to define mapping
8
- def mappings(hash = {}, &block)
9
- @mapping = Esse::IndexMapping.new(body: hash, paths: template_dirs, filenames: mapping_filenames)
10
- return unless block
11
-
12
- @mapping.define_singleton_method(:to_h, &block)
13
- end
14
-
15
- # This is the actually content that will be passed through the ES api
16
- def mappings_hash
17
- hash = mapping.body
18
- {
19
- type_name => (hash.key?('properties') ? hash : { 'properties' => hash }),
20
- }
21
- end
22
-
23
- private
24
-
25
- def mapping
26
- @mapping ||= Esse::IndexMapping.new(paths: template_dirs, filenames: mapping_filenames)
27
- end
28
-
29
- def template_dirs
30
- return [] unless respond_to?(:index)
31
-
32
- index.template_dirs
33
- end
34
-
35
- def mapping_filenames
36
- Esse::IndexMapping::FILENAMES.map { |str| [type_name, str].join('_') }
37
- end
38
- end
39
-
40
- extend ClassMethods
41
- end
42
- end
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'object_document_mapper'
4
-
5
- module Esse
6
- # Type is actually deprecated. Elasticsearch today uses _doc instead of type
7
- # And in upcoming release it will be totally removed.
8
- # But I want to keep compatibility with old versions of es.
9
- class IndexType
10
- require_relative 'index_type/actions'
11
- require_relative 'index_type/mappings'
12
- require_relative 'index_type/backend'
13
- extend ObjectDocumentMapper
14
- end
15
- end
@@ -1,110 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Esse
4
- module ObjectDocumentMapper
5
- # Convert ruby object to json. Arguments will be same of passed through the
6
- # collection. It's allowed a block or a class with the `to_h` instance method.
7
- # Example with block
8
- # serializer do |model, **context|
9
- # {
10
- # id: model.id,
11
- # admin: context[:is_admin],
12
- # }
13
- # end
14
- # Example with serializer class
15
- # serializer UserSerializer
16
- def serializer(klass = nil, &block)
17
- if block
18
- @serializer_proc = block
19
- elsif klass.is_a?(Class) && klass.instance_methods.include?(:to_h)
20
- @serializer_proc = proc { |*args, **kwargs| klass.new(*args, **kwargs).to_h }
21
- elsif klass.is_a?(Class) && klass.instance_methods.include?(:as_json) # backward compatibility
22
- @serializer_proc = proc { |*args, **kwargs| klass.new(*args, **kwargs).as_json }
23
- elsif klass.is_a?(Class) && klass.instance_methods.include?(:call)
24
- @serializer_proc = proc { |*args, **kwargs| klass.new(*args, **kwargs).call }
25
- else
26
- msg = format('%<arg>p is not a valid serializer. The serializer should ' \
27
- 'respond with `to_h` instance method.', arg: klass,)
28
- raise ArgumentError, msg
29
- end
30
- end
31
-
32
- def serialize(model, *args, **kwargs)
33
- unless @serializer_proc
34
- raise NotImplementedError, format('there is no serializer defined for the %<k>p index', k: to_s)
35
- end
36
-
37
- @serializer_proc.call(model, *args, **kwargs)
38
- end
39
-
40
- # Used to define the source of data. A block is required. And its
41
- # content should yield an array of each object that should be serialized.
42
- # The list of arguments will be passed throught the serializer method.
43
- #
44
- # Here is an example of how this should work:
45
- # collection do |conditions, &block|
46
- # User.where(conditions).find_in_batches(batch_size: 5000) do |batch|
47
- # block.call(batch, conditions)
48
- # end
49
- # end
50
- def collection(collection_class = nil, &block)
51
- raise ArgumentError, 'a collection class or a block is required' if block.nil? && collection_class.nil?
52
-
53
- if block.nil? && collection_class.is_a?(Class) && !collection_class.include?(Enumerable)
54
- msg = '%<arg>p is not a valid collection class.' \
55
- ' Collections should implement the Enumerable interface'
56
- raise ArgumentError, format(msg, arg: collection_class)
57
- end
58
-
59
- @collection_proc = collection_class || block
60
- end
61
-
62
- # Used to fetch all batch of data defined on the collection model.
63
- # Arguments can be anything. They will just be passed through the block.
64
- # Useful when the collection depends on scope or any other conditions
65
- # Example
66
- # each_batch(active: true) do |data, _opts|
67
- # puts data.size
68
- # end
69
- def each_batch(*args, **kwargs, &block)
70
- unless @collection_proc
71
- raise NotImplementedError, format('there is no collection defined for the %<k>p index', k: to_s)
72
- end
73
-
74
- case @collection_proc
75
- when Class
76
- @collection_proc.new(*args, **kwargs).each(&block)
77
- else
78
- @collection_proc.call(*args, **kwargs, &block)
79
- end
80
- rescue LocalJumpError
81
- raise(SyntaxError, 'block must be explicitly declared in the collection definition')
82
- end
83
-
84
- # Wrap collection data into serialized batches
85
- #
86
- # @param args [*Object] Any argument is allowed here. The collection will be called with same arguments.
87
- # And the serializer will be initialized with those arguments too.
88
- # @yield [Array, *Object] serialized collection and method arguments
89
- def each_serialized_batch(**kwargs, &block)
90
- each_batch(**kwargs) do |*batch, **collection_kwargs|
91
- entries = batch.flatten.map { |entry| serialize(entry, **collection_kwargs) }.compact
92
- block.call(entries, **kwargs)
93
- end
94
- end
95
-
96
- # Wrap collection data into serialized documents
97
- #
98
- # Example:
99
- # GeosIndex.documents(id: 1).first
100
- #
101
- # @return [Enumerator] All serialized entries
102
- def documents(**kwargs)
103
- Enumerator.new do |yielder|
104
- each_serialized_batch(**kwargs) do |documents, **_collection_kargs|
105
- documents.each { |document| yielder.yield(document) }
106
- end
107
- end
108
- end
109
- end
110
- end