esse 0.0.2 → 0.1.1

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/exec/esse +3 -1
  3. data/lib/esse/backend/index/aliases.rb +8 -4
  4. data/lib/esse/backend/index/close.rb +54 -0
  5. data/lib/esse/backend/index/create.rb +21 -10
  6. data/lib/esse/backend/index/delete.rb +15 -14
  7. data/lib/esse/backend/index/documents.rb +2 -2
  8. data/lib/esse/backend/index/existance.rb +2 -3
  9. data/lib/esse/backend/index/open.rb +54 -0
  10. data/lib/esse/backend/index/refresh.rb +43 -0
  11. data/lib/esse/backend/index/reset.rb +33 -0
  12. data/lib/esse/backend/index/update.rb +128 -4
  13. data/lib/esse/backend/index.rb +20 -4
  14. data/lib/esse/backend/index_type/documents.rb +53 -42
  15. data/lib/esse/backend/index_type.rb +7 -2
  16. data/lib/esse/cli/event_listener.rb +87 -0
  17. data/lib/esse/cli/generate.rb +9 -4
  18. data/lib/esse/cli/index/base_operation.rb +76 -0
  19. data/lib/esse/cli/index/close.rb +26 -0
  20. data/lib/esse/cli/index/create.rb +26 -0
  21. data/lib/esse/cli/index/delete.rb +26 -0
  22. data/lib/esse/cli/index/open.rb +26 -0
  23. data/lib/esse/cli/index/reset.rb +26 -0
  24. data/lib/esse/cli/index/update_aliases.rb +32 -0
  25. data/lib/esse/cli/index/update_mapping.rb +33 -0
  26. data/lib/esse/cli/index/update_settings.rb +26 -0
  27. data/lib/esse/cli/index.rb +70 -2
  28. data/lib/esse/cli/templates/config.rb.erb +20 -0
  29. data/lib/esse/cli/templates/index.rb.erb +76 -11
  30. data/lib/esse/cli/templates/type_collection.rb.erb +41 -0
  31. data/lib/esse/cli/templates/{mappings.json → type_mappings.json} +0 -0
  32. data/lib/esse/cli/templates/type_serializer.rb.erb +23 -0
  33. data/lib/esse/cli.rb +75 -3
  34. data/lib/esse/cluster.rb +22 -6
  35. data/lib/esse/config.rb +39 -5
  36. data/lib/esse/core.rb +18 -36
  37. data/lib/esse/errors.rb +47 -0
  38. data/lib/esse/events/bus.rb +103 -0
  39. data/lib/esse/events/event.rb +64 -0
  40. data/lib/esse/events/publisher.rb +119 -0
  41. data/lib/esse/events.rb +49 -0
  42. data/lib/esse/index/backend.rb +2 -1
  43. data/lib/esse/index/base.rb +4 -6
  44. data/lib/esse/index/mappings.rb +4 -6
  45. data/lib/esse/index/settings.rb +6 -8
  46. data/lib/esse/index.rb +2 -1
  47. data/lib/esse/index_mapping.rb +2 -2
  48. data/lib/esse/index_setting.rb +8 -4
  49. data/lib/esse/index_type/actions.rb +2 -1
  50. data/lib/esse/index_type/backend.rb +2 -1
  51. data/lib/esse/index_type/mappings.rb +3 -3
  52. data/lib/esse/index_type.rb +6 -1
  53. data/lib/esse/logging.rb +19 -0
  54. data/lib/esse/object_document_mapper.rb +96 -0
  55. data/lib/esse/primitives/hash_utils.rb +29 -0
  56. data/lib/esse/primitives/hstring.rb +4 -3
  57. data/lib/esse/primitives/output.rb +64 -0
  58. data/lib/esse/primitives.rb +1 -0
  59. data/lib/esse/template_loader.rb +1 -1
  60. data/lib/esse/version.rb +1 -1
  61. data/lib/esse.rb +14 -2
  62. metadata +127 -24
  63. data/.gitignore +0 -12
  64. data/.rubocop.yml +0 -128
  65. data/CHANGELOG.md +0 -0
  66. data/Gemfile +0 -7
  67. data/Gemfile.lock +0 -60
  68. data/LICENSE.txt +0 -21
  69. data/README.md +0 -50
  70. data/Rakefile +0 -4
  71. data/bin/console +0 -22
  72. data/bin/setup +0 -8
  73. data/esse.gemspec +0 -39
  74. data/lib/esse/cli/templates/serializer.rb.erb +0 -14
  75. data/lib/esse/index_type/serializer.rb +0 -87
  76. data/lib/esse/types/mapping.rb +0 -0
@@ -2,12 +2,80 @@
2
2
 
3
3
  require 'thor'
4
4
  require_relative 'base'
5
+
5
6
  module Esse
6
7
  module CLI
7
8
  class Index < Base
8
- desc 'create *INDEX_CLASSES', 'Creates a new index'
9
+ desc 'reset *INDEX_CLASSES', 'Performs zero-downtime index resetting.'
10
+ long_desc <<-DESC
11
+ This task is used to rebuild index with zero-downtime. The task will:
12
+ * Creates a new index using the suffix defined on index class or from CLI.
13
+ * Import documents using the Index collection.
14
+ * Update alias to point to the new index.
15
+ * Delete the old index.
16
+ DESC
17
+ option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
18
+ def reset(*index_classes)
19
+ require_relative 'index/reset'
20
+ Reset.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
21
+ end
22
+
23
+ desc 'create *INDEX_CLASSES', 'Creates indices for the given classes'
24
+ long_desc <<-DESC
25
+ Creates index and applies mapping and settings for the given classes.
26
+
27
+ Indices are created with the following naming convention:
28
+ <cluster.index_prefix>_<index_class.index_name>_<index_class.index_version>.
29
+ DESC
30
+ option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
31
+ option :alias, type: :boolean, default: false, aliases: '-a', desc: 'Update alias after create index'
9
32
  def create(*index_classes)
10
- # Add action here
33
+ require_relative 'index/create'
34
+ Create.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
35
+ end
36
+
37
+ desc 'delete *INDEX_CLASSES', 'Deletes indices for the given classes'
38
+ option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
39
+ def delete(*index_classes)
40
+ require_relative 'index/delete'
41
+ Delete.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
42
+ end
43
+
44
+ desc 'update_aliases *INDEX_CLASS', 'Replaces all existing aliases by the given suffix'
45
+ option :suffix, type: :string, aliases: '-s', desc: 'Suffix to append to index name'
46
+ def update_aliases(*index_classes)
47
+ require_relative 'index/update_aliases'
48
+ UpdateAliases.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
49
+ end
50
+
51
+ desc 'update_settings *INDEX_CLASS', 'Closes the index for read/write operations, updates the index settings, and open it again'
52
+ option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
53
+ option :type, type: :string, default: nil, aliases: '-t', desc: 'Document Type to update mapping for'
54
+ def update_settings(*index_classes)
55
+ require_relative 'index/update_settings'
56
+ UpdateSettings.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
57
+ end
58
+
59
+ desc 'update_mapping *INDEX_CLASS', 'Create or update a mapping'
60
+ option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
61
+ option :type, type: :string, default: nil, aliases: '-t', desc: 'Document Type to update mapping for'
62
+ def update_mapping(*index_classes)
63
+ require_relative 'index/update_mapping'
64
+ UpdateMapping.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
65
+ end
66
+
67
+ desc 'close *INDEX_CLASS', 'Close an index (keep the data on disk, but deny operations with the index).'
68
+ option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
69
+ def close(*index_classes)
70
+ require_relative 'index/close'
71
+ Close.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
72
+ end
73
+
74
+ desc 'open *INDEX_CLASS', 'Open a previously closed index.'
75
+ option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
76
+ def open(*index_classes)
77
+ require_relative 'index/open'
78
+ Open.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
11
79
  end
12
80
  end
13
81
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ Esse.configure do |config|
4
+ config.indices_directory = File.expand_path('../../app/indices', __FILE__)
5
+ config.cluster(:default) do |cluster|
6
+ # Default index prefix
7
+ cluster.index_prefix = "esse"
8
+ # you also can add environment to the index prefix if available:
9
+ # cluster.index_prefix = "esse_#{ENV['RACK_ENV']}"
10
+ #
11
+ # Initialize the ElastiSearch client to be used by the default cluster
12
+ cluster.client = Elasticsearch::Client.new
13
+
14
+ # Global index settings
15
+ # cluster.index_settings = {
16
+ # number_of_shards: 5,
17
+ # number_of_replicas: 0,
18
+ # }
19
+ end
20
+ end
@@ -1,11 +1,75 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ <%- @types.each do |type| -%>
4
+ require_relative '<%= @index_name.demodulize.underscore.to_s %>/collections/<%= type.underscore %>_collection'
5
+ <%- end -%>
6
+ <%- @types.each do |type| -%>
7
+ require_relative '<%= @index_name.demodulize.underscore.to_s %>/serializers/<%= type.underscore %>_serializer'
8
+ <%- end -%>
9
+
3
10
  class <%= @index_name %> < <%= @base_class %>
4
11
  # plugin :active_record
5
12
  # plugin :sequel
6
- <%- @types.each do |type| -%>
7
13
 
8
- define_type :<%= type %> do
14
+ <%- if @types.empty? -%>
15
+ # Collection
16
+ # ==========
17
+ #
18
+ # Collection is the source of data for the index. We hightly recommend using a
19
+ # another class that implements the Enumerable interface to better split the
20
+ # responsabilities and easy to test.
21
+ #
22
+ # collection Collections::DocumentCollection
23
+ #
24
+ # but you can also use block definition style:
25
+ # collection do |_context, &block|
26
+ # block.call [{ title: 'foo' }, { title: 'bar' }], context
27
+ # end
28
+ #
29
+ # Serializer block or class yielder should be called with an array of objects.
30
+ # Each these objects should be serialized using the serializer in described in the next section.
31
+ # The number of objects will be indexed to elasticsearch using the bulk api. So adjust the
32
+ # number of objects to be indexed accordingly.
33
+ #
34
+ # Here is a good place to eager loading data from database or any other repository.
35
+ # The bellow example is a rails like application that could preload using activerecord
36
+ #
37
+ # collection do |**context, &block|
38
+ # query = <%= @index_name.camelize %>.all
39
+ # query = query.where(**context[:conditions]) if context[:conditions]
40
+ # query = query.includes(:user) if context[:include_user]
41
+ # query.find_in_batches(batch_size: 5000) do |batch|
42
+ # block.call batch, context
43
+ # end
44
+ # end
45
+
46
+
47
+ # Serializer
48
+ # ==========
49
+ #
50
+ # Serializer is the class responsible for serializing indexing documents.
51
+ # Each object yielded by the collection will be serialized on this step.
52
+ # We recommend using a another class to handle the serialization. The only requirement is
53
+ # that the class implements the `#to_h` method.
54
+ # The serializer class may also be initialized with context if collection block is called with that extra parameter.
55
+ # Ability to call serializer with context is useful for preloading data from database or any other repository.
56
+ #
57
+ # serializer Serializers::DocumentSerializer
58
+ #
59
+ # You can also serialize the collection entry using a block:
60
+ #
61
+ # serializer do |model, context|
62
+ # hash = {
63
+ # name: <%= @index_name.underscore %>.name,
64
+ # }
65
+ # # Context is just an example here. But it's useful for eager loading data.
66
+ # # I'll think a better example when implement this idea.
67
+ # hash[:some_attribute] = <%= @index_name.underscore %>.some_attribute if context[:include_some_attribute]
68
+ # hash
69
+ # end
70
+ <%- end -%>
71
+ <%- @types.each do |type| -%>
72
+ define_type :<%= type.underscore %> do
9
73
  # Collection
10
74
  # ==========
11
75
  #
@@ -14,19 +78,19 @@ class <%= @index_name %> < <%= @base_class %>
14
78
  # Useful for eager loading data from database or any other repository. Below is an example of a rails like
15
79
  # application could load using activerecord.
16
80
  #
17
- # collection do |conditions|
18
- # context = {}
19
- # <%= type.camelize %>.where(conditions).find_in_batches(batch_size: 5000) do |batch|
20
- # yield batch, context, ...
81
+ # collection do |context, &block|
82
+ # <%= type.camelize %>.where(context[:conditions]).find_in_batches(batch_size: 5000) do |batch|
83
+ # block.call batch, context
21
84
  # end
22
85
  # end
86
+ collection Collections::<%= type.camelize %>Collection
23
87
  #
24
88
  #
25
89
  # Serializer
26
90
  # ==========
27
91
  #
28
- # The serializer can be any class that respond with the `as_json` class method.
29
- # And the result of its as_json is a Hash.
92
+ # The serializer can be any class that respond with the `to_h` class method.
93
+ # And the result of its to_h is a Hash.
30
94
  #
31
95
  # Here is an example of a simple serializer:
32
96
  # app/serializers/<%= type %>_serializer.rb
@@ -35,17 +99,17 @@ class <%= @index_name %> < <%= @base_class %>
35
99
  # @<%= type %> = <%= type %>
36
100
  # end
37
101
  #
38
- # def as_json
102
+ # def to_h
39
103
  # { '_id' => @<%= type %>.id, 'name' => @<%= type %>.name }
40
104
  # end
41
105
  # end
42
106
  #
43
107
  # And here you specify your serializer classe.
44
- # serializer <%= @index_name %>::Serializers::<%= type.camelize %>Serializer
108
+ # serializer Serializers::<%= type.camelize %>Serializer
45
109
  #
46
110
  # You can also serialize the collection entry using a block:
47
111
  #
48
- # serializer(<%= type %>, context = {}) do
112
+ # serializer do |model, **context|
49
113
  # hash = {
50
114
  # name: <%= type %>.name,
51
115
  # }
@@ -54,6 +118,7 @@ class <%= @index_name %> < <%= @base_class %>
54
118
  # hash[:some_attribute] = <%= type %>.some_attribute if context[:include_some_attribute]
55
119
  # hash
56
120
  # end
121
+ serializer Serializers::<%= type.camelize %>Serializer
57
122
  end
58
123
  <%- end -%>
59
124
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= @index_name %> < <%= @base_class %>
4
+ module Collections
5
+ class <%= @type.camelize %>Collection
6
+ include Enumerable
7
+
8
+ # @param params [Hash] List of parameters
9
+ def initialize(**params)
10
+ @params = params
11
+ end
12
+
13
+ # Find all <%= @type %> in batches
14
+ #
15
+ # @yield [Array<<%= @type.camelize %>>]
16
+ # @see <%= @index_name %>::<%= @type.camelize %>#collection
17
+ def each
18
+ offset = 0
19
+ while (rows = find_all(offset))
20
+ break if rows.none?
21
+
22
+ # You may preload associations before serialize them
23
+ # associations = preload_associations!(rows)
24
+ # yield(row, associations)
25
+ offset += 1
26
+
27
+ yield(rows, **params)
28
+ end
29
+
30
+ protected
31
+
32
+ attr_reader :params
33
+
34
+ # @param offset [Number] Offset to start from
35
+ def find_all(offset)
36
+ # @TODO load data from persistent store
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= @index_name %> < <%= @base_class %>
4
+ module Serializers
5
+ class <%= @type.camelize %>Serializer
6
+ def initialize(<%= @type %>, **params)
7
+ @entity = <%= @type %>
8
+ @params = params
9
+ end
10
+
11
+ def to_h
12
+ {
13
+ id: entity.id, # This field is required
14
+ name: entity.name,
15
+ }
16
+ end
17
+
18
+ protected
19
+
20
+ attr_reader :entity, :params
21
+ end
22
+ end
23
+ end
data/lib/esse/cli.rb CHANGED
@@ -2,16 +2,48 @@
2
2
 
3
3
  require 'thor'
4
4
 
5
+ require_relative 'primitives/output'
5
6
  require_relative 'cli/index'
6
7
  require_relative 'cli/generate'
8
+ require_relative 'cli/event_listener'
7
9
 
8
10
  module Esse
9
11
  module CLI
10
- def self.start(*args)
11
- Root.start(*args)
12
+ class << self
13
+ def start(*args)
14
+ Root.start(*args)
15
+ end
16
+
17
+ def with_friendly_errors
18
+ yield
19
+ rescue CLI::Error => e
20
+ Output.print_error(e)
21
+ exit(1)
22
+ end
12
23
  end
13
24
 
14
25
  class Root < Thor
26
+ include Thor::Actions
27
+
28
+ class_option :require, type: :string, aliases: '-r', required: false,
29
+ default: nil, desc: 'Require config file where the application is defined'
30
+ class_option :silent, type: :boolean, aliases: '-s', required: false, default: false, desc: 'Silent mode'
31
+
32
+ def initialize(*)
33
+ super
34
+
35
+ load_app_config(options[:require])
36
+ setup_listeners if !options[:silent] && Esse.config.cli_event_listeners?
37
+ end
38
+
39
+ def self.source_root
40
+ File.expand_path('../cli', __FILE__)
41
+ end
42
+
43
+ def self.exit_on_failure?
44
+ true
45
+ end
46
+
15
47
  map %w[--version -v] => :version
16
48
 
17
49
  desc 'index SUBCOMMAND ...ARGS', 'Manage indices'
@@ -22,7 +54,47 @@ module Esse
22
54
 
23
55
  desc '--version, -v', 'Show package version'
24
56
  def version
25
- puts format('Esse version: %<version>s', version: Esse::VERSION)
57
+ Output.print_message('Esse version: %<version>s', version: Esse::VERSION)
58
+ end
59
+
60
+ desc 'install', 'Generate boilerplate configuration files'
61
+ option :path, type: :string, aliases: '-p', required: false, default: './'
62
+ def install
63
+ path = Pathname.new(File.expand_path(options[:path], Dir.pwd))
64
+ path = path.dirname unless path.directory?
65
+ @app_dir = path.basename
66
+ template(
67
+ 'templates/config.rb.erb',
68
+ path.join('config/esse.rb'),
69
+ )
70
+ end
71
+
72
+ private
73
+
74
+ def setup_listeners
75
+ Esse::Events.__bus__.events.keys.grep(/^elasticsearch/).each do |event_name|
76
+ Esse::Events.subscribe(event_name) do |event|
77
+ EventListener[event_name]&.call(event)
78
+ end
79
+ end
80
+ end
81
+
82
+ def load_app_config(path)
83
+ if path.nil?
84
+ CLI_CONFIG_PATHS.each do |config_path|
85
+ next unless File.exist?(config_path)
86
+ path = config_path
87
+ break
88
+ end
89
+ end
90
+ return unless path
91
+
92
+ begin
93
+ Output.print_message('Loading configuration file: %<path>s', path: path)
94
+ load path
95
+ rescue LoadError => e
96
+ raise InvalidOption, e.message
97
+ end
26
98
  end
27
99
  end
28
100
  end
data/lib/esse/cluster.rb CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  module Esse
4
4
  class Cluster
5
- ATTRIBUTES = %i[index_prefix index_settings client].freeze
5
+ ATTRIBUTES = %i[index_prefix index_settings client wait_for_status].freeze
6
+ WAIT_FOR_STATUSES = %w[green yellow red].freeze
6
7
 
7
8
  # The index prefix. For example an index named UsersIndex.
8
9
  # With `index_prefix = 'app1'`. Final index/alias is: 'app1_users'
@@ -11,6 +12,15 @@ module Esse
11
12
  # This settings will be passed through all indices during the mapping
12
13
  attr_accessor :index_settings
13
14
 
15
+ # if this option set, actions such as creating or deleting index,
16
+ # importing data will wait for the status specified. Extremely useful
17
+ # for tests under heavy indices manipulations.
18
+ # Value can be set to `red`, `yellow` or `green`.
19
+ #
20
+ # Example:
21
+ # wait_for_status: green
22
+ attr_accessor :wait_for_status
23
+
14
24
  attr_reader :id
15
25
 
16
26
  def initialize(id:, **options)
@@ -35,14 +45,14 @@ module Esse
35
45
  end
36
46
 
37
47
  # Define the elasticsearch client connectio
38
- # @param client [Elasticsearch::Client, Hash] an instance of elasticsearch/api client or an hash
48
+ # @param es_client [Elasticsearch::Client, Hash] an instance of elasticsearch/api client or an hash
39
49
  # with the settings that will be used to initialize Elasticsearch::Client
40
- def client=(val)
41
- @client = if val.is_a?(Hash)
42
- settings = val.each_with_object({}) { |(k,v), r| r[k.to_sym] = v }
50
+ def client=(es_client)
51
+ @client = if es_client.is_a?(Hash)
52
+ settings = es_client.each_with_object({}) { |(k, v), r| r[k.to_sym] = v }
43
53
  Elasticsearch::Client.new(settings)
44
54
  else
45
- val
55
+ es_client
46
56
  end
47
57
  end
48
58
 
@@ -54,5 +64,11 @@ module Esse
54
64
  attrs << format('client=%p', @client)
55
65
  format('#<Esse::Cluster %<attrs>s>', attrs: attrs.join(' '))
56
66
  end
67
+
68
+ def wait_for_status!(status: wait_for_status)
69
+ return unless WAIT_FOR_STATUSES.include?(status.to_s)
70
+
71
+ client.cluster.health(wait_for_status: status.to_s)
72
+ end
57
73
  end
58
74
  end
data/lib/esse/config.rb CHANGED
@@ -3,6 +3,31 @@
3
3
  require 'pathname'
4
4
 
5
5
  module Esse
6
+ # Block configurations
7
+ # Esse.configure do |conf|
8
+ # conf.indices_directory = 'app/indices/directory'
9
+ # conf.cluster(:v1) do |cluster|
10
+ # cluster.index_prefix = 'backend'
11
+ # cluster.client = Elasticsearch::Client.new
12
+ # cluster.index_settings = {
13
+ # number_of_shards: 2,
14
+ # number_of_replicas: 0
15
+ # }
16
+ # end
17
+ # end
18
+ #
19
+ # Inline configurations
20
+ # Esse.config.indices_directory = 'app/indices/directory'
21
+ # Esse.config.cluster(:v1).client = Elasticsearch::Client.new
22
+ class << self
23
+ def config
24
+ @config ||= Config.new
25
+ yield(@config) if block_given?
26
+ @config
27
+ end
28
+ alias_method :configure, :config
29
+ end
30
+
6
31
  # Provides all configurations
7
32
  #
8
33
  # Example
@@ -19,14 +44,14 @@ module Esse
19
44
  def initialize
20
45
  self.indices_directory = 'app/indices'
21
46
  @clusters = {}
22
- clusters(DEFAULT_CLUSTER_ID) # initialize the :default client
47
+ cluster(DEFAULT_CLUSTER_ID) # initialize the :default client
23
48
  end
24
49
 
25
50
  def cluster_ids
26
51
  @clusters.keys
27
52
  end
28
53
 
29
- def clusters(key = DEFAULT_CLUSTER_ID, **options)
54
+ def cluster(key = DEFAULT_CLUSTER_ID, **options)
30
55
  return unless key
31
56
 
32
57
  id = key.to_sym
@@ -35,6 +60,7 @@ module Esse
35
60
  yield c if block_given?
36
61
  end
37
62
  end
63
+ alias_method :clusters, :cluster
38
64
 
39
65
  def indices_directory=(value)
40
66
  @indices_directory = value.is_a?(Pathname) ? value : Pathname.new(value)
@@ -45,14 +71,22 @@ module Esse
45
71
  when Hash
46
72
  assign(arg)
47
73
  when File, Pathname
48
- # @TODO Load JSON or YAML
74
+ assign(YAML.load_file(arg))
49
75
  when String
50
- # @TODO Load JSON or YAML if File.exist?(arg)
76
+ return load(Pathname.new(arg)) if File.exist?(arg)
77
+
78
+ assign(YAML.safe_load(arg))
51
79
  else
52
80
  raise ArgumentError, printf('could not load configuration using: %p', val)
53
81
  end
54
82
  end
55
83
 
84
+ # :nodoc:
85
+ # This is only used by rspec to disable the CLI print out.
86
+ def cli_event_listeners?
87
+ true
88
+ end
89
+
56
90
  private
57
91
 
58
92
  def assign(hash)
@@ -64,7 +98,7 @@ module Esse
64
98
  end
65
99
  if (connections = hash['clusters'] || hash[:clusters]).is_a?(Hash)
66
100
  connections.each do |key, value|
67
- clusters(key).assign(value) if value.is_a?(Hash)
101
+ cluster(key).assign(value) if value.is_a?(Hash)
68
102
  end
69
103
  end
70
104
  true
data/lib/esse/core.rb CHANGED
@@ -1,35 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+
3
4
  require 'multi_json'
4
5
  require 'elasticsearch'
5
6
 
6
7
  module Esse
8
+ require_relative 'config'
9
+ require_relative 'cluster'
10
+ require_relative 'primitives'
11
+ require_relative 'index_type'
12
+ require_relative 'index_setting'
13
+ require_relative 'index_mapping'
14
+ require_relative 'template_loader'
15
+ require_relative 'backend/index'
16
+ require_relative 'backend/index_type'
17
+ require_relative 'version'
18
+ require_relative 'logging'
19
+ require_relative 'events'
20
+ include Logging
21
+
7
22
  @single_threaded = false
8
23
  # Mutex used to protect mutable data structures
9
24
  @data_mutex = Mutex.new
10
25
 
11
- # Block configurations
12
- # Esse.config do |conf|
13
- # conf.indices_directory = 'app/indices/directory'
14
- # conf.clusters(:v1) do |cluster|
15
- # cluster.index_prefix = 'backend'
16
- # cluster.client = Elasticsearch::Client.new
17
- # cluster.index_settings = {
18
- # number_of_shards: 2,
19
- # number_of_replicas: 0
20
- # }
21
- # end
22
- # end
23
- #
24
- # Inline configurations
25
- # Esse.config.indices_directory = 'app/indices/directory'
26
- # Esse.config.clusters(:v1).client = Elasticsearch::Client.new
27
- def self.config
28
- @config ||= Config.new
29
- yield(@config) if block_given?
30
- @config
31
- end
32
-
33
26
  # Unless in single threaded mode, protects access to any mutable
34
27
  # global data structure in Esse.
35
28
  # Uses a non-reentrant mutex, so calling code should be careful.
@@ -48,9 +41,9 @@ module Esse
48
41
 
49
42
  # Simple helper used to fetch Hash value using Symbol and String keys.
50
43
  #
51
- # @param [Hash] the JSON document
52
- # @option [Array] :delete Removes the hash key and return its value
53
- # @option [Array] :keep Fetch the hash key and return its value
44
+ # @param hash [Hash] the JSON document
45
+ # @param delete [Array] Removes the hash key and return its value
46
+ # @param keep [Array] Fetch the hash key and return its value
54
47
  # @return [Array([Integer, String, nil], Hash)] return the key value and the modified hash
55
48
  def self.doc_id!(hash, delete: %w[_id], keep: %w[id])
56
49
  return unless hash.is_a?(Hash)
@@ -75,15 +68,4 @@ module Esse
75
68
  end
76
69
  [id, modified]
77
70
  end
78
-
79
- require_relative 'config'
80
- require_relative 'cluster'
81
- require_relative 'primitives'
82
- require_relative 'index_type'
83
- require_relative 'index_setting'
84
- require_relative 'index_mapping'
85
- require_relative 'template_loader'
86
- require_relative 'backend/index'
87
- require_relative 'backend/index_type'
88
- require_relative 'version'
89
71
  end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Esse
4
+ class Error < StandardError
5
+ end
6
+
7
+ module Events
8
+ class UnregisteredEventError < ::Esse::Error
9
+ def initialize(object_or_event_id)
10
+ case object_or_event_id
11
+ when String, Symbol
12
+ super("You are trying to publish an unregistered event: `#{object_or_event_id}`")
13
+ else
14
+ super('You are trying to publish an unregistered event')
15
+ end
16
+ end
17
+ end
18
+
19
+ class InvalidSubscriberError < ::Esse::Error
20
+ # @api private
21
+ def initialize(object_or_event_id)
22
+ case object_or_event_id
23
+ when String, Symbol
24
+ super("you are trying to subscribe to an event: `#{object_or_event_id}` that has not been registered")
25
+ else
26
+ super('you try use subscriber object that will never be executed')
27
+ end
28
+ end
29
+ end
30
+ end
31
+
32
+ module CLI
33
+ class Error < ::Esse::Error
34
+ def initialize(msg = nil, **message_attributes)
35
+ if message_attributes.any?
36
+ msg = format(msg, **message_attributes)
37
+ end
38
+ super(msg)
39
+ end
40
+ end
41
+
42
+ class InvalidOption < Error
43
+ end
44
+ end
45
+
46
+ # Elasticsearch::Transport::Transport::Errors::NotFound
47
+ end