esse 0.0.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 (56) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rubocop.yml +128 -0
  4. data/CHANGELOG.md +0 -0
  5. data/Gemfile +7 -0
  6. data/Gemfile.lock +60 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +50 -0
  9. data/Rakefile +4 -0
  10. data/bin/console +22 -0
  11. data/bin/setup +8 -0
  12. data/esse.gemspec +39 -0
  13. data/exec/esse +9 -0
  14. data/lib/esse.rb +7 -0
  15. data/lib/esse/backend/index.rb +38 -0
  16. data/lib/esse/backend/index/aliases.rb +69 -0
  17. data/lib/esse/backend/index/create.rb +56 -0
  18. data/lib/esse/backend/index/delete.rb +38 -0
  19. data/lib/esse/backend/index/documents.rb +23 -0
  20. data/lib/esse/backend/index/existance.rb +23 -0
  21. data/lib/esse/backend/index/update.rb +19 -0
  22. data/lib/esse/backend/index_type.rb +32 -0
  23. data/lib/esse/backend/index_type/documents.rb +203 -0
  24. data/lib/esse/cli.rb +29 -0
  25. data/lib/esse/cli/base.rb +11 -0
  26. data/lib/esse/cli/generate.rb +51 -0
  27. data/lib/esse/cli/index.rb +14 -0
  28. data/lib/esse/cli/templates/index.rb.erb +59 -0
  29. data/lib/esse/cli/templates/mappings.json +6 -0
  30. data/lib/esse/cli/templates/serializer.rb.erb +14 -0
  31. data/lib/esse/cluster.rb +58 -0
  32. data/lib/esse/config.rb +73 -0
  33. data/lib/esse/core.rb +89 -0
  34. data/lib/esse/index.rb +21 -0
  35. data/lib/esse/index/actions.rb +10 -0
  36. data/lib/esse/index/backend.rb +13 -0
  37. data/lib/esse/index/base.rb +118 -0
  38. data/lib/esse/index/descendants.rb +17 -0
  39. data/lib/esse/index/inheritance.rb +18 -0
  40. data/lib/esse/index/mappings.rb +47 -0
  41. data/lib/esse/index/naming.rb +64 -0
  42. data/lib/esse/index/settings.rb +48 -0
  43. data/lib/esse/index/type.rb +31 -0
  44. data/lib/esse/index_mapping.rb +38 -0
  45. data/lib/esse/index_setting.rb +41 -0
  46. data/lib/esse/index_type.rb +10 -0
  47. data/lib/esse/index_type/actions.rb +11 -0
  48. data/lib/esse/index_type/backend.rb +13 -0
  49. data/lib/esse/index_type/mappings.rb +42 -0
  50. data/lib/esse/index_type/serializer.rb +87 -0
  51. data/lib/esse/primitives.rb +3 -0
  52. data/lib/esse/primitives/hstring.rb +85 -0
  53. data/lib/esse/template_loader.rb +46 -0
  54. data/lib/esse/types/mapping.rb +0 -0
  55. data/lib/esse/version.rb +5 -0
  56. metadata +215 -0
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ class Index
5
+ module ClassMethods
6
+ def descendants # :nodoc:
7
+ descendants = []
8
+ ObjectSpace.each_object(singleton_class) do |k|
9
+ descendants.unshift k unless k == self
10
+ end
11
+ descendants.uniq
12
+ end
13
+ end
14
+
15
+ extend ClassMethods
16
+ end
17
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ class Index
5
+ module ClassMethods
6
+ # Set this to +true+ if this is an abstract class
7
+ attr_accessor :abstract_class
8
+
9
+ def abstract_class?
10
+ return @abstract_class == true if defined?(@abstract_class)
11
+
12
+ !index_name?
13
+ end
14
+ end
15
+
16
+ extend ClassMethods
17
+ end
18
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ # The es 7.6 deprecate the mapping definition under the type level. That's why we have option
4
+ # to define mappings under both Type and Index. If the index mapping is defined. All the Type
5
+ # mapping will be ignored.
6
+ # Source: https://www.elastic.co/guide/en/elasticsearch/reference/7.6/removal-of-types.html
7
+ module Esse
8
+ class Index
9
+ module ClassMethods
10
+ MAPPING_ROOT_KEY = 'mappings'
11
+
12
+ # This is the actually content that will be passed through the ES api
13
+ def mappings_hash
14
+ { MAPPING_ROOT_KEY => (index_mapping || type_mapping) }
15
+ end
16
+
17
+ # This method is only used to define mapping
18
+ def mappings(hash = {}, &block)
19
+ @mapping = Esse::IndexMapping.new(body: hash, paths: template_dirs)
20
+ return unless block_given?
21
+
22
+ @mapping.define_singleton_method(:as_json, &block)
23
+ end
24
+
25
+ private
26
+
27
+ def mapping
28
+ @mapping ||= Esse::IndexMapping.new(paths: template_dirs)
29
+ end
30
+
31
+ def index_mapping
32
+ return if mapping.empty?
33
+
34
+ hash = mapping.body
35
+ hash.key?(MAPPING_ROOT_KEY) ? hash[MAPPING_ROOT_KEY] : hash
36
+ end
37
+
38
+ def type_mapping
39
+ return {} if type_hash.empty?
40
+
41
+ type_hash.values.map(&:mappings_hash).reduce(&:merge)
42
+ end
43
+ end
44
+
45
+ extend ClassMethods
46
+ end
47
+ end
@@ -0,0 +1,64 @@
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
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ # https://github.com/elastic/elasticsearch-ruby/blob/master/elasticsearch-api/lib/elasticsearch/api/actions/indices/put_settings.rb
5
+ class Index
6
+ module ClassMethods
7
+ SETTING_ROOT_KEY = 'settings'
8
+
9
+ def settings_hash
10
+ hash = setting.body
11
+ { SETTING_ROOT_KEY => (hash.key?(SETTING_ROOT_KEY) ? hash[SETTING_ROOT_KEY] : hash) }
12
+ end
13
+
14
+ # Define /_settings definition by each index.
15
+ #
16
+ # +hash+: The body of the request includes the updated settings.
17
+ # +block+: Overwrite default :as_json from IndexSetting instance
18
+ #
19
+ # Example:
20
+ #
21
+ # class UserIndex < Esse::Index
22
+ # settings {
23
+ # number_of_replicas: 4,
24
+ # }
25
+ # end
26
+ #
27
+ # class UserIndex < Esse::Index
28
+ # settings do
29
+ # # do something to load settings..
30
+ # end
31
+ # end
32
+ def settings(hash = {}, &block)
33
+ @setting = Esse::IndexSetting.new(body: hash, paths: template_dirs, globals: cluster.index_settings)
34
+ return unless block_given?
35
+
36
+ @setting.define_singleton_method(:as_json, &block)
37
+ end
38
+
39
+ private
40
+
41
+ def setting
42
+ @setting ||= Esse::IndexSetting.new(paths: template_dirs, globals: cluster.index_settings)
43
+ end
44
+ end
45
+
46
+ extend ClassMethods
47
+ end
48
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ class Index
5
+ module ClassMethods
6
+ attr_writer :type_hash
7
+
8
+ def type_hash
9
+ @type_hash ||= {}
10
+ end
11
+
12
+ def define_type(type_name, &block)
13
+ type_class = Class.new(Esse::IndexType)
14
+
15
+ const_set(Hstring.new(type_name).camelize.demodulize.to_s, type_class)
16
+
17
+ index = self
18
+
19
+ type_class.send(:define_singleton_method, :index) { index }
20
+ type_class.send(:define_singleton_method, :type_name) { type_name.to_s }
21
+
22
+ type_class.class_eval(&block) if block
23
+
24
+ self.type_hash = type_hash.merge(type_class.type_name => type_class)
25
+ type_class
26
+ end
27
+ end
28
+
29
+ extend ClassMethods
30
+ end
31
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ class IndexMapping
5
+ FILENAMES = %w[mapping mappings].freeze
6
+
7
+ def initialize(body: {}, paths: [], filenames: FILENAMES)
8
+ @paths = Array(paths)
9
+ @filenames = Array(filenames)
10
+ @mappings = body
11
+ end
12
+
13
+ # This method will be overwrited when passing a block during the
14
+ # mapping defination
15
+ def as_json
16
+ return @mappings unless @mappings.empty?
17
+
18
+ from_template || @mappings
19
+ end
20
+
21
+ def body
22
+ as_json
23
+ end
24
+
25
+ def empty?
26
+ body.empty?
27
+ end
28
+
29
+ protected
30
+
31
+ def from_template
32
+ return if @paths.empty?
33
+
34
+ loader = Esse::TemplateLoader.new(@paths)
35
+ loader.read(*@filenames)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ # https://www.elastic.co/guide/en/elasticsearch/reference/1.7/indices.html
5
+ class IndexSetting
6
+ def initialize(body: {}, paths: [], globals: {})
7
+ @globals = globals || {}
8
+ @paths = Array(paths)
9
+ @settings = body
10
+ end
11
+
12
+ # This method will be overwrited when passing a block during the settings
13
+ # defination on index class.
14
+ #
15
+ # Example:
16
+ # class UserIndex < Esse::Index
17
+ # settings do
18
+ # # do something to load settings..
19
+ # end
20
+ # end
21
+ #
22
+ def as_json
23
+ return @settings unless @settings.empty?
24
+
25
+ from_template || @settings
26
+ end
27
+
28
+ def body
29
+ @globals.merge(as_json)
30
+ end
31
+
32
+ protected
33
+
34
+ def from_template
35
+ return if @paths.empty?
36
+
37
+ loader = Esse::TemplateLoader.new(@paths)
38
+ loader.read('{setting,settings}')
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ class IndexType
5
+ require_relative 'index_type/actions'
6
+ require_relative 'index_type/mappings'
7
+ require_relative 'index_type/serializer'
8
+ require_relative 'index_type/backend'
9
+ end
10
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ class IndexType
5
+ module ClassMethods
6
+ def action(name, options = {}, &block); end
7
+ end
8
+
9
+ extend ClassMethods
10
+ end
11
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ class IndexType
5
+ module ClassMethods
6
+ def backend
7
+ Esse::Backend::IndexType.new(self)
8
+ end
9
+ end
10
+
11
+ extend ClassMethods
12
+ end
13
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ class IndexType
5
+ # https://github.com/elastic/elasticsearch-ruby/blob/master/elasticsearch-api/lib/elasticsearch/api/actions/indices/put_mapping.rb
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_given?
11
+
12
+ @mapping.define_singleton_method(:as_json, &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
@@ -0,0 +1,87 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ class IndexType
5
+ module ClassMethods
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 `as_json` instance method.
8
+ # Example with block
9
+ # serializer 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(klass = nil, &block)
18
+ if block_given?
19
+ @serializer_proc = block
20
+ elsif klass.is_a?(Class) && klass.instance_methods.include?(:as_json)
21
+ @serializer_proc = proc { |*args| klass.new(*args).as_json }
22
+ elsif klass.is_a?(Class) && klass.instance_methods.include?(:call)
23
+ @serializer_proc = proc { |*args| klass.new(*args).call }
24
+ else
25
+ msg = format('%<arg>p is not a valid serializer. The serializer should ' \
26
+ 'respond with `as_json` instance method.', arg: klass,)
27
+ raise ArgumentError, msg
28
+ end
29
+ end
30
+
31
+ def serialize(model, *args)
32
+ unless @serializer_proc
33
+ raise NotImplementedError, format('there is no serializer defined for the %<k>p index', k: to_s)
34
+ end
35
+
36
+ @serializer_proc.call(model, *args)
37
+ end
38
+
39
+ # Used to define the source of data. A block is required. And its
40
+ # content should yield an array of each object that should be serialized.
41
+ # The list of arguments will be passed throught the serializer method.
42
+ #
43
+ # Here is an example of how this should work:
44
+ # collection do |conditions, &block|
45
+ # User.where(conditions).find_in_batches(batch_size: 5000) do |batch|
46
+ # block.call(batch, conditions)
47
+ # end
48
+ # end
49
+ def collection(&block)
50
+ raise(SyntaxError, 'No block given') unless block_given?
51
+
52
+ @collection_proc = block
53
+ end
54
+
55
+ # Used to fetch all batch of data defined on the collection model.
56
+ # Arguments can be anything. They will just be passed through the block.
57
+ # Useful when the collection depends on scope or any other conditions
58
+ # Example
59
+ # each_batch(active: true) do |data, _opts|
60
+ # puts data.size
61
+ # end
62
+ def each_batch(*args, &block)
63
+ unless @collection_proc
64
+ raise NotImplementedError, format('there is no collection defined for the %<k>p index', k: to_s)
65
+ end
66
+
67
+ @collection_proc.call(*args, &block)
68
+ rescue LocalJumpError
69
+ raise(SyntaxError, 'block must be explicitly declared in the collection definition')
70
+ end
71
+
72
+ # Wrap collection data into serialized batches
73
+ #
74
+ # @param [*Object] Any argument is allowed here. The collection will be called with same arguments.
75
+ # And the serializer will be initialized with those arguments too.
76
+ # @yield [Array, *Object] serialized collection and method arguments
77
+ def each_serialized_batch(*args, &block)
78
+ each_batch(*args) do |batch, *serializer_args|
79
+ entries = batch.map { |entry| serialize(entry, *serializer_args) }.compact
80
+ block.call(entries, *args)
81
+ end
82
+ end
83
+ end
84
+
85
+ extend ClassMethods
86
+ end
87
+ end