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.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rubocop.yml +128 -0
- data/CHANGELOG.md +0 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +60 -0
- data/LICENSE.txt +21 -0
- data/README.md +50 -0
- data/Rakefile +4 -0
- data/bin/console +22 -0
- data/bin/setup +8 -0
- data/esse.gemspec +39 -0
- data/exec/esse +9 -0
- data/lib/esse.rb +7 -0
- data/lib/esse/backend/index.rb +38 -0
- data/lib/esse/backend/index/aliases.rb +69 -0
- data/lib/esse/backend/index/create.rb +56 -0
- data/lib/esse/backend/index/delete.rb +38 -0
- data/lib/esse/backend/index/documents.rb +23 -0
- data/lib/esse/backend/index/existance.rb +23 -0
- data/lib/esse/backend/index/update.rb +19 -0
- data/lib/esse/backend/index_type.rb +32 -0
- data/lib/esse/backend/index_type/documents.rb +203 -0
- data/lib/esse/cli.rb +29 -0
- data/lib/esse/cli/base.rb +11 -0
- data/lib/esse/cli/generate.rb +51 -0
- data/lib/esse/cli/index.rb +14 -0
- data/lib/esse/cli/templates/index.rb.erb +59 -0
- data/lib/esse/cli/templates/mappings.json +6 -0
- data/lib/esse/cli/templates/serializer.rb.erb +14 -0
- data/lib/esse/cluster.rb +58 -0
- data/lib/esse/config.rb +73 -0
- data/lib/esse/core.rb +89 -0
- data/lib/esse/index.rb +21 -0
- data/lib/esse/index/actions.rb +10 -0
- data/lib/esse/index/backend.rb +13 -0
- data/lib/esse/index/base.rb +118 -0
- data/lib/esse/index/descendants.rb +17 -0
- data/lib/esse/index/inheritance.rb +18 -0
- data/lib/esse/index/mappings.rb +47 -0
- data/lib/esse/index/naming.rb +64 -0
- data/lib/esse/index/settings.rb +48 -0
- data/lib/esse/index/type.rb +31 -0
- data/lib/esse/index_mapping.rb +38 -0
- data/lib/esse/index_setting.rb +41 -0
- data/lib/esse/index_type.rb +10 -0
- data/lib/esse/index_type/actions.rb +11 -0
- data/lib/esse/index_type/backend.rb +13 -0
- data/lib/esse/index_type/mappings.rb +42 -0
- data/lib/esse/index_type/serializer.rb +87 -0
- data/lib/esse/primitives.rb +3 -0
- data/lib/esse/primitives/hstring.rb +85 -0
- data/lib/esse/template_loader.rb +46 -0
- data/lib/esse/types/mapping.rb +0 -0
- data/lib/esse/version.rb +5 -0
- 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,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
|