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