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