esse 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
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