esse 0.2.0 → 0.2.2

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 (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
@@ -0,0 +1,96 @@
1
+ module Esse
2
+ module Import
3
+ class Bulk
4
+ def initialize(index: nil, delete: nil, create: nil)
5
+ @index = Array(index).select(&method(:valid_doc?)).reject(&:ignore_on_index?).map do |doc|
6
+ { index: doc.to_bulk }
7
+ end
8
+ @create = Array(create).select(&method(:valid_doc?)).reject(&:ignore_on_index?).map do |doc|
9
+ { create: doc.to_bulk }
10
+ end
11
+ @delete = Array(delete).select(&method(:valid_doc?)).reject(&:ignore_on_delete?).map do |doc|
12
+ { delete: doc.to_bulk(data: false) }
13
+ end
14
+ end
15
+
16
+ # Return an array of RequestBody instances
17
+ #
18
+ # In case of timeout error, will retry with an exponential backoff using the following formula:
19
+ # wait_interval = (retry_count**4) + 15 + (rand(10) * (retry_count + 1)) seconds. It will retry up to max_retries times that is default 3.
20
+ #
21
+ # Too large bulk requests will be split into multiple requests with only one attempt.
22
+ #
23
+ # @yield [RequestBody] A request body instance
24
+ def each_request(max_retries: 3)
25
+ requests = [optimistic_request]
26
+ retry_count = 0
27
+
28
+ begin
29
+ requests.each do |request|
30
+ yield(request) if request.body?
31
+ end
32
+ rescue Faraday::TimeoutError, Esse::Backend::RequestTimeoutError => e
33
+ retry_count += 1
34
+ raise Esse::Backend::RequestTimeoutError.new(e.message) if retry_count >= max_retries
35
+ wait_interval = (retry_count**4) + 15 + (rand(10) * (retry_count + 1))
36
+ Esse.logger.warn "Timeout error, retrying in #{wait_interval} seconds"
37
+ sleep(wait_interval)
38
+ retry
39
+ rescue Esse::Backend::RequestEntityTooLargeError => e
40
+ retry_count += 1
41
+ raise e if retry_count > 1 # only retry once on this error
42
+ requests = balance_requests_size(e)
43
+ Esse.logger.warn <<~MSG
44
+ Request entity too large, retrying with a bulk with: #{requests.map(&:bytesize).join(" + ")}.
45
+ Note that this cause performance degradation, consider adjusting the batch_size of the index or increasing the bulk size.
46
+ MSG
47
+ retry
48
+ end
49
+ end
50
+
51
+ private
52
+
53
+ def valid_doc?(doc)
54
+ doc && doc.is_a?(Esse::Serializer) && doc.id
55
+ end
56
+
57
+ def optimistic_request
58
+ request = Import::RequestBodyAsJson.new
59
+ request.delete = @delete
60
+ request.create = @create
61
+ request.index = @index
62
+ request
63
+ end
64
+
65
+ # @return [Array<RequestBody>]
66
+ def balance_requests_size(err)
67
+ if (bulk_size = err.message.scan(/exceeded.(\d+).bytes/).dig(0, 0).to_i) > 0
68
+ requests = (@delete + @create + @index).each_with_object([Import::RequestBodyRaw.new]) do |as_json, result|
69
+ operation, meta = as_json.to_a.first
70
+ meta = meta.dup
71
+ data = meta.delete(:data)
72
+ piece = MultiJson.dump(operation => meta)
73
+ piece << "\n" << MultiJson.dump(data) if data
74
+ if piece.bytesize > bulk_size
75
+ Esse.logger.warn <<~MSG
76
+ The document #{meta.inspect} size is #{piece.bytesize} bytes, which exceeds the maximum bulk size of #{bulk_size} bytes.
77
+ Consider increasing the bulk size or reducing the document size. The document will be ignored during this import.
78
+ MSG
79
+ next
80
+ end
81
+
82
+ if result.last.body.bytesize + piece.bytesize > bulk_size
83
+ result.push(Import::RequestBodyRaw.new.tap { |r| r.add(operation, piece) })
84
+ else
85
+ result[-1].add(operation, piece)
86
+ end
87
+ end
88
+ requests.each(&:finalize)
89
+ else
90
+ raise err
91
+ end
92
+ end
93
+
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,60 @@
1
+ module Esse
2
+ module Import
3
+ class RequestBody
4
+ attr_reader :body, :stats
5
+
6
+ def initialize(body:)
7
+ @body = body # body may be String or Array<Hash>
8
+ @stats = { index: 0, create: 0, delete: 0 }
9
+ end
10
+
11
+ def body?
12
+ !body.empty?
13
+ end
14
+ end
15
+
16
+ class RequestBodyRaw < RequestBody
17
+ def initialize
18
+ super(body: '')
19
+ end
20
+
21
+ def bytesize
22
+ body.bytesize
23
+ end
24
+
25
+ def add(operation, payload)
26
+ stats[operation] += 1
27
+ if @body.empty?
28
+ @body = payload
29
+ else
30
+ @body << "\n" << payload
31
+ end
32
+ end
33
+
34
+ def finalize
35
+ @body << "\n"
36
+ end
37
+ end
38
+
39
+ class RequestBodyAsJson < RequestBody
40
+ def initialize
41
+ super(body: [])
42
+ end
43
+
44
+ def index=(docs)
45
+ @body += docs
46
+ @stats[:index] += docs.size
47
+ end
48
+
49
+ def create=(docs)
50
+ @body += docs
51
+ @stats[:create] += docs.size
52
+ end
53
+
54
+ def delete=(docs)
55
+ @body += docs
56
+ @stats[:delete] += docs.size
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,98 @@
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 = Hstring.new(value.to_s).underscore.presence
13
+ end
14
+
15
+ def index_name(suffix: nil)
16
+ iname = index_prefixed_name(@index_name || normalized_name)
17
+ suffix = Hstring.new(suffix).underscore.presence
18
+ return iname if !iname || !suffix
19
+
20
+ [iname, suffix].join('_')
21
+ end
22
+
23
+ def index_name?
24
+ !index_name.nil?
25
+ end
26
+
27
+ def index_prefix
28
+ return @index_prefix if defined? @index_prefix
29
+
30
+ cluster.index_prefix
31
+ end
32
+
33
+ def index_prefix=(value)
34
+ return @index_prefix = nil if value == false
35
+
36
+ @index_prefix = Hstring.new(value.to_s).underscore.presence
37
+ end
38
+
39
+ def index_version=(value)
40
+ @index_version = Hstring.new(value.to_s).underscore.presence
41
+ end
42
+
43
+ def index_version
44
+ @index_version
45
+ end
46
+
47
+ def uname
48
+ Hstring.new(name).underscore.presence
49
+ end
50
+
51
+ def index_directory
52
+ return unless uname
53
+ return if uname == 'Esse::Index'
54
+
55
+ Esse.config.indices_directory.join(uname).to_s
56
+ end
57
+
58
+ def template_dirs
59
+ return [] unless index_directory
60
+
61
+ TEMPLATE_DIRS.map { |term| format(term, dirname: index_directory) }
62
+ end
63
+
64
+ def bulk_wait_interval
65
+ @bulk_wait_interval || Esse.config.bulk_wait_interval
66
+ end
67
+
68
+ def bulk_wait_interval=(value)
69
+ @bulk_wait_interval = value.to_f
70
+ end
71
+
72
+ def mapping_single_type=(value)
73
+ @mapping_single_type = !!value
74
+ end
75
+
76
+ def mapping_single_type?
77
+ return @mapping_single_type if defined? @mapping_single_type
78
+
79
+ @mapping_single_type = cluster.engine.mapping_single_type?
80
+ end
81
+
82
+ protected
83
+
84
+ def index_prefixed_name(value)
85
+ return if value == '' || value.nil?
86
+ return value.to_s unless index_prefix
87
+
88
+ [index_prefix, value].join('_')
89
+ end
90
+
91
+ def normalized_name
92
+ Hstring.new(name).underscore.tr('/', '_').sub(/_(index)$/, '')
93
+ end
94
+ end
95
+
96
+ extend ClassMethods
97
+ end
98
+ end
@@ -67,7 +67,7 @@ module Esse
67
67
  raise ArgumentError.new, format(msg, arg: source, ids: valid_ids.map(&:inspect).join(', '))
68
68
  end
69
69
 
70
- klass.type_hash = {}
70
+ klass.repo_hash = {}
71
71
  klass
72
72
  end
73
73
 
@@ -11,6 +11,36 @@ module Esse
11
11
 
12
12
  !index_name?
13
13
  end
14
+
15
+ def inherited(subclass)
16
+ super
17
+
18
+ inherited_instance_variables.each do |variable_name, should_duplicate|
19
+ if (variable_value = instance_variable_get(variable_name)) && should_duplicate
20
+ value = case variable_value
21
+ when Hash
22
+ h = {}
23
+ variable_value.each { |k, v| h[k] = v.dup }
24
+ h
25
+ else
26
+ variable_value.dup
27
+ end
28
+ end
29
+ subclass.instance_variable_set(variable_name, value)
30
+ end
31
+ end
32
+
33
+ private
34
+
35
+ def inherited_instance_variables
36
+ {
37
+ :@repo_hash => nil,
38
+ :@setting => nil,
39
+ :@mapping => nil,
40
+ :@cluster_id => :dup,
41
+ :@plugins => :dup,
42
+ }
43
+ end
14
44
  end
15
45
 
16
46
  extend ClassMethods
@@ -7,36 +7,23 @@
7
7
  module Esse
8
8
  class Index
9
9
  module ClassMethods
10
- # This is the actually content that will be passed through the ES api
11
- def mappings_hash
12
- { Esse::MAPPING_ROOT_KEY => (index_mapping || type_mapping) }
13
- end
14
-
15
10
  # This method is only used to define mapping
16
11
  def mappings(hash = {}, &block)
17
- @mapping = Esse::IndexMapping.new(body: hash, paths: template_dirs)
12
+ @mapping = Esse::IndexMapping.new(body: hash, paths: template_dirs, globals: -> { cluster.mappings })
18
13
  return unless block
19
14
 
20
15
  @mapping.define_singleton_method(:to_h, &block)
21
16
  end
22
17
 
23
- private
24
-
25
- def mapping
26
- @mapping ||= Esse::IndexMapping.new(paths: template_dirs)
27
- end
28
-
29
- def index_mapping
30
- return if mapping.empty?
31
-
18
+ def mappings_hash
32
19
  hash = mapping.body
33
- hash.key?(Esse::MAPPING_ROOT_KEY) ? hash[Esse::MAPPING_ROOT_KEY] : hash
20
+ { Esse::MAPPING_ROOT_KEY => (hash.key?(Esse::MAPPING_ROOT_KEY) ? hash[Esse::MAPPING_ROOT_KEY] : hash) }
34
21
  end
35
22
 
36
- def type_mapping
37
- return {} if type_hash.empty?
23
+ private
38
24
 
39
- type_hash.values.map(&:mappings_hash).reduce(&:merge)
25
+ def mapping
26
+ @mapping ||= Esse::IndexMapping.new(paths: template_dirs, globals: -> { cluster.mappings })
40
27
  end
41
28
  end
42
29
 
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ class Index
5
+ module ObjectDocumentMapper
6
+ # Convert ruby object to json. Arguments will be same of passed through the
7
+ # collection. It's allowed a block or a class with the `to_h` instance method.
8
+ # Example with block
9
+ # serializer :user do |model, **context|
10
+ # {
11
+ # id: model.id,
12
+ # admin: context[:is_admin],
13
+ # }
14
+ # end
15
+ # Example with serializer class
16
+ # serializer UserSerializer
17
+ def serializer(*args, &block)
18
+ repo_name, klass = args
19
+ # >> Backward compatibility for the old collection syntax without explicit repo_name
20
+ if repo_name && klass.nil? && !repo_name.is_a?(String) && !repo_name.is_a?(Symbol)
21
+ klass = repo_name
22
+ repo_name = DEFAULT_REPO_NAME
23
+ end
24
+ repo_name = repo_name&.to_s || DEFAULT_REPO_NAME
25
+ # <<
26
+ find_or_define_repo(repo_name).serializer(klass, &block)
27
+ end
28
+
29
+ # Used to define the source of data. A block is required. And its
30
+ # content should yield an array of each object that should be serialized.
31
+ # The list of arguments will be passed throught the serializer method.
32
+ #
33
+ # Example:
34
+ # collection :admin, AdminStore
35
+ # collection :user do |**conditions, &block|
36
+ # User.where(conditions).find_in_batches(batch_size: 5000) do |batch|
37
+ # block.call(batch, conditions)
38
+ # end
39
+ # end
40
+ #
41
+ # @param [String] name The identification of the collection.
42
+ # @param [Class] klass The class of the collection. (Optional when block is passed)
43
+ # @param [Proc] block The block that will be used to iterate over the collection. (Optional when using a class)
44
+ # @return [void]
45
+ def collection(*args, **kwargs, &block)
46
+ repo_name, collection_klass = args
47
+ # >> Backward compatibility for the old collection syntax without explicit repo_name
48
+ if repo_name && !repo_name.is_a?(Symbol) && !repo_name.is_a?(String) && collection_klass.nil?
49
+ collection_klass = repo_name
50
+ repo_name = DEFAULT_REPO_NAME
51
+ end
52
+ repo_name = repo_name&.to_s || DEFAULT_REPO_NAME
53
+ # <<
54
+ find_or_define_repo(repo_name).collection(collection_klass, **kwargs, &block)
55
+ end
56
+
57
+ # Wrap collection data into serialized batches
58
+ #
59
+ # @param [String, NilClass] repo_name The repository identifier
60
+ # @param [Hash] kwargs The context
61
+ # @return [Enumerator] The enumerator
62
+ # @yield [Array, **context] serialized collection and the optional context from the collection
63
+ def each_serialized_batch(repo_name = nil, **kwargs, &block)
64
+ (repo_name ? [repo(repo_name)] : repo_hash.values).each do |repo|
65
+ repo.each_serialized_batch(**kwargs, &block)
66
+ end
67
+ end
68
+
69
+ # Wrap collection data into serialized documents
70
+ #
71
+ # Example:
72
+ # GeosIndex.documents(id: 1).first
73
+ #
74
+ # @param [String, NilClass] repo_name The repository identifier
75
+ # @return [Enumerator] All serialized entries
76
+ def documents(repo_name = nil, **kwargs)
77
+ Enumerator.new do |yielder|
78
+ each_serialized_batch(repo_name, **kwargs) do |documents, **_collection_kargs|
79
+ documents.each { |document| yielder.yield(document) }
80
+ end
81
+ end
82
+ end
83
+
84
+ private
85
+
86
+ def find_or_define_repo(repo_name)
87
+ return repo_hash[repo_name] if repo_hash.key?(repo_name)
88
+
89
+ repository(repo_name) {}
90
+ end
91
+ end
92
+
93
+ extend ObjectDocumentMapper
94
+ end
95
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ class Index
5
+ module ClassMethods
6
+ attr_reader :plugins
7
+
8
+ def plugin(plugin, **kwargs, &block)
9
+ mod = plugin.is_a?(Module) ? plugin : load_plugin_module(plugin)
10
+
11
+ unless @plugins.include?(mod)
12
+ @plugins << mod
13
+ mod.apply(self, **kwargs, &block) if mod.respond_to?(:apply)
14
+ extend(mod::IndexClassMethods) if mod.const_defined?(:IndexClassMethods, false)
15
+ if mod.const_defined?(:RepositoryClassMethods, false)
16
+ repo_hash.each_value.each { |repo| repository_plugin_extend(repo, mod::RepositoryClassMethods) }
17
+ end
18
+ end
19
+
20
+ mod.configure(self, **kwargs, &block) if mod.respond_to?(:configure)
21
+ end
22
+
23
+ private
24
+
25
+ def repository_plugin_extend(repo_class, mod)
26
+ return if repo_class.singleton_class.included_modules.include?(mod)
27
+
28
+ repo_class.extend(mod)
29
+ end
30
+
31
+ def load_plugin_module(name)
32
+ module_name = Hstring.new(name)
33
+ unless Esse::Plugins.const_defined?(module_name.camelize.to_s, false)
34
+ require "esse/plugins/#{module_name.underscore}"
35
+ end
36
+ Esse::Plugins.const_get(module_name.camelize.to_s)
37
+ end
38
+ end
39
+
40
+ extend ClassMethods
41
+ end
42
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ class Index
5
+ module ClassMethods
6
+ # @param query_or_payload [String,Hash] The search request definition or query in the Lucene query string syntax
7
+ # @param kwargs [Hash] The options to pass to the search.
8
+ def search(*args, &block)
9
+ query_or_payload = args.shift
10
+ kwargs = args.last.is_a?(Hash) ? args.pop : {}
11
+
12
+ if query_or_payload.respond_to?(:to_hash) && (hash = query_or_payload.to_hash) && (hash.key?(:body) || hash.key?('body') || hash.key?(:q) || hash.key?('q'))
13
+ kwargs.merge!(hash.transform_keys(&:to_sym))
14
+ elsif query_or_payload.respond_to?(:to_hash)
15
+ kwargs[:body] = query_or_payload.to_hash
16
+ elsif query_or_payload.is_a?(String) && query_or_payload =~ /^\s*{/
17
+ kwargs[:body] = MultiJson.load(query_or_payload)
18
+ elsif query_or_payload.is_a?(String)
19
+ kwargs[:q] = query_or_payload
20
+ end
21
+ cluster.search(self, **kwargs, &block)
22
+ end
23
+ end
24
+
25
+ extend ClassMethods
26
+ end
27
+ end
@@ -28,7 +28,7 @@ module Esse
28
28
  # end
29
29
  # end
30
30
  def settings(hash = {}, &block)
31
- @setting = Esse::IndexSetting.new(body: hash, paths: template_dirs, globals: -> { cluster.index_settings })
31
+ @setting = Esse::IndexSetting.new(body: hash, paths: template_dirs, globals: -> { cluster.settings })
32
32
  return unless block
33
33
 
34
34
  @setting.define_singleton_method(:to_h, &block)
@@ -37,7 +37,7 @@ module Esse
37
37
  private
38
38
 
39
39
  def setting
40
- @setting ||= Esse::IndexSetting.new(paths: template_dirs, globals: -> { cluster.index_settings })
40
+ @setting ||= Esse::IndexSetting.new(paths: template_dirs, globals: -> { cluster.settings })
41
41
  end
42
42
  end
43
43
 
@@ -3,26 +3,67 @@
3
3
  module Esse
4
4
  class Index
5
5
  module ClassMethods
6
- attr_writer :type_hash
6
+ attr_writer :repo_hash
7
7
 
8
- def type_hash
9
- @type_hash ||= {}
8
+ def repo_hash
9
+ @repo_hash ||= {}
10
10
  end
11
11
 
12
- def define_type(type_name, &block)
13
- type_class = Class.new(Esse::IndexType)
12
+ def repo(name = nil)
13
+ if name.nil? && repo_hash.size == 1
14
+ name = repo_hash.keys.first
15
+ elsif name.nil? && repo_hash.size > 1
16
+ raise ArgumentError, "You can only call `repo' with a name when there is only one type defined."
17
+ end
18
+ name ||= DEFAULT_REPO_NAME
14
19
 
15
- const_set(Hstring.new(type_name).camelize.demodulize.to_s, type_class)
20
+ repo_hash.fetch(name.to_s)
21
+ rescue KeyError
22
+ raise ArgumentError, <<~MSG
23
+ No repo named "#{name}" found. Use the `repository' method to define one:
24
+
25
+ repository :#{name} do
26
+ # collection ...
27
+ # serializer ...
28
+ end
29
+ MSG
30
+ end
31
+
32
+ def repo?(name = nil)
33
+ return repo_hash.size > 0 if name.nil?
34
+
35
+ repo_hash.key?(name.to_s)
36
+ end
37
+
38
+ def repository(repo_name, *_args, **kwargs, &block)
39
+ repo_class = Class.new(Esse::Repository)
40
+ kwargs[:const] ||= true # TODO Change this to false to avoid collisions with application classes
41
+ kwargs[:lazy_evaluate] ||= false
42
+
43
+ if kwargs[:const]
44
+ const_set(Hstring.new(repo_name).camelize.demodulize.to_s, repo_class)
45
+ end
16
46
 
17
47
  index = self
18
48
 
19
- type_class.send(:define_singleton_method, :index) { index }
20
- type_class.send(:define_singleton_method, :type_name) { type_name.to_s }
49
+ repo_class.send(:define_singleton_method, :index) { index }
50
+ repo_class.send(:define_singleton_method, :repo_name) { repo_name.to_s }
51
+ repo_class.document_type = (kwargs[:document_type] || repo_name).to_s
52
+
53
+ plugins.each do |mod|
54
+ next unless mod.const_defined?(:RepositoryClassMethods, false)
55
+
56
+ repository_plugin_extend(repo_class, mod::RepositoryClassMethods)
57
+ end
58
+
59
+ if kwargs[:lazy_evaluate]
21
60
 
22
- type_class.class_eval(&block) if block
61
+ elsif block
62
+ repo_class.class_eval(&block)
63
+ end
23
64
 
24
- self.type_hash = type_hash.merge(type_class.type_name => type_class)
25
- type_class
65
+ self.repo_hash = repo_hash.merge(repo_class.repo_name => repo_class)
66
+ repo_class
26
67
  end
27
68
  end
28
69
 
data/lib/esse/index.rb CHANGED
@@ -1,21 +1,25 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'object_document_mapper'
4
-
5
3
  module Esse
6
4
  class Index
5
+ @repo_hash = {}
6
+ @setting = {}
7
+ @mapping = {}
8
+ @plugins = []
9
+ @cluster_id = nil
10
+
11
+ require_relative 'index/plugins'
7
12
  require_relative 'index/base'
8
13
  require_relative 'index/inheritance'
9
14
  require_relative 'index/actions'
10
- require_relative 'index/naming'
15
+ require_relative 'index/attributes'
11
16
  require_relative 'index/type'
12
17
  require_relative 'index/settings'
13
18
  require_relative 'index/mappings'
14
19
  require_relative 'index/descendants'
15
20
  require_relative 'index/backend'
16
- extend ObjectDocumentMapper
17
-
18
- @cluster_id = nil
21
+ require_relative 'index/object_document_mapper'
22
+ require_relative 'index/search'
19
23
 
20
24
  def_Index(::Esse)
21
25
  end
@@ -4,10 +4,11 @@ module Esse
4
4
  class IndexMapping
5
5
  FILENAMES = %w[mapping mappings].freeze
6
6
 
7
- def initialize(body: {}, paths: [], filenames: FILENAMES)
7
+ def initialize(body: {}, paths: [], filenames: FILENAMES, globals: nil)
8
8
  @paths = Array(paths)
9
9
  @filenames = Array(filenames)
10
10
  @mappings = body
11
+ @globals = globals || -> { {} }
11
12
  end
12
13
 
13
14
  # This method will be overwrited when passing a block during the
@@ -19,7 +20,14 @@ module Esse
19
20
  end
20
21
 
21
22
  def body
22
- to_h
23
+ global = HashUtils.deep_transform_keys(@globals.call, &:to_sym)
24
+ local = HashUtils.deep_transform_keys(to_h.dup, &:to_sym)
25
+ dynamic_template = DynamicTemplate.new(global[:dynamic_templates])
26
+ dynamic_template.merge!(local.delete(:dynamic_templates))
27
+ if dynamic_template.any?
28
+ global[:dynamic_templates] = dynamic_template.to_a
29
+ end
30
+ HashUtils.deep_merge(global, local)
23
31
  end
24
32
 
25
33
  def empty?
@@ -30,7 +30,9 @@ module Esse
30
30
  end
31
31
 
32
32
  def body
33
- HashUtils.deep_merge(@globals.call, to_h)
33
+ global = HashUtils.deep_transform_keys(@globals.call, &:to_sym)
34
+ local = HashUtils.deep_transform_keys(to_h, &:to_sym)
35
+ HashUtils.deep_merge(global, local)
34
36
  end
35
37
 
36
38
  protected