realm-core 0.7.0

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0f61dae355ea08d78da40bc227540ce47635d09ac0f8645d3f70e1e53201ff54
4
+ data.tar.gz: 57bfe266b33d2f7744b87af84b14a2bd0d7a3c25c0bfc6b452457488067ad181
5
+ SHA512:
6
+ metadata.gz: 3568eca1b26cd974a6d860e233cb50d4911e49bd7f61f4d6c43d865e78cbded0d48dd40e6331fa8d46ce3eb8906a7093c797ce340e16e33c4fad441085199c72
7
+ data.tar.gz: cca39336c0d199d46254e15293439b9e81f401b8e4a586f68eb90658a5e027888f4bce1911dad21dbbd6fb2a6c4c68e4380d47935d1492c962e84a092496f8de
data/lib/realm-core.rb ADDED
@@ -0,0 +1,6 @@
1
+ # rubocop:disable Naming/FileName
2
+ # frozen_string_literal: true
3
+
4
+ require 'realm'
5
+
6
+ # rubocop:enable Naming/FileName
data/lib/realm.rb ADDED
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ Dir[File.join(File.dirname(__FILE__), 'realm', '**', '*.rb')].sort.each do |f|
4
+ require f
5
+ end
6
+
7
+ module Realm
8
+ class << self
9
+ # Setup realm in test/console
10
+ def setup(root_module, **options)
11
+ config = Realm::Config.new(root_module: root_module, **options)
12
+ Realm::Builder.setup(config)
13
+ end
14
+
15
+ # Bind realm in service/engine
16
+ def bind(root_module, **options)
17
+ setup(root_module, **options).tap do |builder|
18
+ root_module.define_singleton_method(:realm) { builder.runtime }
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-validation'
4
+ require 'active_support/core_ext/module/delegation'
5
+ require 'realm/error'
6
+ require 'realm/mixins/context_injection'
7
+ require 'realm/mixins/repository_helper'
8
+ require 'realm/mixins/aggregate_member'
9
+ require_relative 'action_handler/result'
10
+
11
+ module Realm
12
+ class ActionHandler
13
+ extend Mixins::ContextInjection::ClassMethods
14
+ include Mixins::AggregateMember
15
+ include Mixins::RepositoryHelper
16
+
17
+ class << self
18
+ attr_reader :contracts
19
+
20
+ def call(action: :handle, params: {}, runtime: nil)
21
+ new(runtime: runtime).(action: action, params: params)
22
+ end
23
+
24
+ protected
25
+
26
+ def require_permission(*names)
27
+ # TODO: implement
28
+ end
29
+
30
+ def contract(&block)
31
+ @method_contract = Class.new(Dry::Validation::Contract, &block).new
32
+ end
33
+
34
+ def contract_schema(&block)
35
+ contract { schema(&block) }
36
+ end
37
+
38
+ def contract_params(&block)
39
+ contract { params(&block) }
40
+ end
41
+
42
+ def contract_json(&block)
43
+ contract { json(&block) }
44
+ end
45
+
46
+ def method_added(method_name)
47
+ super
48
+ return unless defined?(@method_contract)
49
+
50
+ @contracts ||= {}
51
+ @contracts[method_name] = @method_contract
52
+ remove_instance_variable(:@method_contract)
53
+ end
54
+ end
55
+
56
+ def initialize(runtime: nil)
57
+ @runtime = runtime
58
+ end
59
+
60
+ def call(action: :handle, params: {})
61
+ # TODO: check permissions
62
+ raise CannotHandleAction.new(self, action) unless respond_to?(action)
63
+
64
+ safe_params = validate(action, params.to_h)
65
+ send(action, **safe_params)
66
+ end
67
+
68
+ protected
69
+
70
+ delegate :context, to: :@runtime
71
+
72
+ private
73
+
74
+ def validate(action, params)
75
+ contract = self.class.contracts && self.class.contracts[action]
76
+ return params unless contract
77
+
78
+ result = contract.(params)
79
+ raise Realm::InvalidParams, result if result.failure?
80
+
81
+ result.to_h
82
+ end
83
+ end
84
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Realm
4
+ class ActionHandler
5
+ # Tuple of label and value
6
+ class Result < Array
7
+ def self.[](first, second = nil)
8
+ return new(first, second).freeze if first.is_a?(Symbol) || first.is_a?(Realm::Event)
9
+
10
+ new(second || :ok, first).freeze
11
+ end
12
+
13
+ def label
14
+ self[0]
15
+ end
16
+
17
+ def event
18
+ label if label.is_a?(Realm::Event)
19
+ end
20
+
21
+ def value
22
+ self[1]
23
+ end
24
+
25
+ private
26
+
27
+ def initialize(label, value)
28
+ super([label, value])
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/string'
4
+ require 'realm/container'
5
+ require 'realm/runtime'
6
+ require 'realm/domain_resolver'
7
+ require 'realm/persistence'
8
+ require 'realm/dispatcher'
9
+ require 'realm/event_router'
10
+ require 'realm/plugin'
11
+
12
+ module Realm
13
+ class Builder
14
+ def self.setup(config)
15
+ new(config).setup
16
+ end
17
+
18
+ def initialize(config)
19
+ @config = config
20
+ end
21
+
22
+ def setup
23
+ logger.info("Setting up #{cfg.root_module} realm")
24
+ register_domain_resolver
25
+ register_event_router
26
+ register_runtime
27
+ register_logger
28
+ register_dependencies
29
+ setup_plugins
30
+ config_persistence
31
+ self
32
+ end
33
+
34
+ def runtime
35
+ @container.resolve(Runtime)
36
+ end
37
+
38
+ private
39
+
40
+ def register_domain_resolver
41
+ container.register_factory(DomainResolver, constantize(cfg.domain_module))
42
+ end
43
+
44
+ def register_event_router
45
+ return if cfg.event_gateways.empty?
46
+
47
+ container.register_factory(EventRouter, cfg.event_gateways, prefix: cfg.prefix)
48
+ end
49
+
50
+ def register_runtime
51
+ container.register_factory(Runtime, container)
52
+ end
53
+
54
+ def register_logger
55
+ container.register(:logger, logger)
56
+ end
57
+
58
+ def register_dependencies
59
+ container.register_all(**cfg.dependencies)
60
+ end
61
+
62
+ def setup_plugins
63
+ Plugin.descendants.each do |plugin|
64
+ plugin.setup(cfg, container) if cfg.plugins.include?(plugin.plugin_name)
65
+ end
66
+ end
67
+
68
+ def config_persistence
69
+ return unless cfg.persistence_gateway.present?
70
+
71
+ Persistence.setup(container, cfg.persistence_gateway[:repositories])
72
+ end
73
+
74
+ def constantize(*parts)
75
+ return parts[0] unless parts[0].is_a?(String)
76
+
77
+ parts.join('::').safe_constantize
78
+ end
79
+
80
+ def cfg
81
+ @config
82
+ end
83
+
84
+ def container
85
+ @container ||= Container.new
86
+ end
87
+
88
+ def logger
89
+ @logger ||= cfg.logger || (defined?(Rails) && Rails.logger) ||
90
+ Logger.new($stdout, level: ENV.fetch('LOG_LEVEL', :info).to_sym)
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/module/delegation'
4
+ require 'active_support/core_ext/string'
5
+ require 'realm/action_handler'
6
+ require 'realm/mixins/reactive'
7
+
8
+ module Realm
9
+ class CommandHandler < Realm::ActionHandler
10
+ include Mixins::Reactive
11
+
12
+ def call(*)
13
+ gateway = context[:rom]&.gateways&.dig(:default)
14
+ gateway ? gateway.transaction { super } : super
15
+ end
16
+
17
+ protected
18
+
19
+ def result(first, second = nil)
20
+ Result[first, second]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/string'
4
+ require 'dry-initializer'
5
+
6
+ module Realm
7
+ class Config
8
+ extend Dry::Initializer
9
+
10
+ option :root_module
11
+ option :database_url, default: proc {}
12
+ option :prefix, default: proc {}
13
+ option :namespace, default: proc { root_module.to_s.underscore }
14
+ option :domain_module, default: proc { "#{root_module}::Domain" }
15
+ option :engine_class, default: proc { "#{root_module}::Engine" }
16
+ option :engine_path, default: proc { engine_class&.to_s&.safe_constantize&.root }
17
+ option :logger, default: proc {}
18
+ option :plugins, default: proc { [] }, reader: false
19
+ option :dependencies, default: proc { {} }
20
+ option :persistence_gateway, default: proc { database_url && { type: :rom, url: database_url } }, reader: false
21
+ option :event_gateway, default: proc {}, reader: false
22
+ option :event_gateways, default: proc {
23
+ @event_gateway ? { default: { **@event_gateway, default: true } } : {}
24
+ }
25
+
26
+ def plugins
27
+ Array(@plugins)
28
+ end
29
+
30
+ def persistence_gateway
31
+ return {} unless @persistence_gateway
32
+
33
+ class_path = engine_path && "#{engine_path}/app/persistence/#{namespace}"
34
+ repos_path = class_path && "#{class_path}/repositories"
35
+ repos_module = "#{root_module}::Repositories"
36
+ {
37
+ root_module: root_module,
38
+ class_path: class_path,
39
+ repos_path: repos_path,
40
+ repos_module: repos_module,
41
+ migration_path: engine_path && "#{engine_path}/db/migrate",
42
+ repositories: repositories(repos_path, repos_module),
43
+ }.merge(@persistence_gateway)
44
+ end
45
+
46
+ private
47
+
48
+ def repositories(repos_path, repos_module)
49
+ return [] unless repos_path
50
+
51
+ Dir[File.join(repos_path, '**', '*.rb')].each_with_object([]) do |filename, all|
52
+ matches = %r{^#{repos_path}/(.+)\.rb$}.match(filename)
53
+ all << "#{repos_module}::#{matches[1].camelize}".constantize if matches
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'dry-container'
4
+ require 'active_support/core_ext/string'
5
+ require 'active_support/core_ext/object/try'
6
+ require 'realm/error'
7
+
8
+ module Realm
9
+ class Container
10
+ include Dry::Container::Mixin
11
+ include Enumerable
12
+
13
+ def self.[](object)
14
+ object.is_a?(Container) ? object : Container.new(object)
15
+ end
16
+
17
+ def initialize(hash = {})
18
+ register_all(hash)
19
+ end
20
+
21
+ def register(key, contents = nil, options = {}, &block)
22
+ options[:klass] ||= contents.class if contents && !contents.is_a?(::Hash)
23
+ super(key, contents, options, &block)
24
+ end
25
+
26
+ def register_all(hash)
27
+ hash.each_pair do |key, value|
28
+ register(key, value)
29
+ end
30
+ end
31
+
32
+ def register_factory(klass, *args, as: nil, memoize: true, **kwargs) # rubocop:disable Naming/MethodParameterName
33
+ register(as || klass, klass: klass, memoize: memoize) do
34
+ create(klass, *args, **kwargs)
35
+ end
36
+ end
37
+
38
+ def create(klass, *args, **kwargs)
39
+ (klass.try(:dependencies) || []).each do |d|
40
+ fn = -> { resolve_dependable(sanitize_dependable(d.dependable), d.optional?) }
41
+ kwargs[d.name] = d.lazy? ? fn : fn.call
42
+ end
43
+ klass.new(*args, **kwargs)
44
+ end
45
+
46
+ def [](key)
47
+ resolve(key) if key?(key)
48
+ end
49
+
50
+ def resolve_all(klass)
51
+ _container.each_with_object([]) do |(_, item), all|
52
+ all << item.call if item.options[:klass] <= klass
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def sanitize_dependable(dependable)
59
+ dependable.is_a?(String) && dependable.match(/^[A-Z]/) ? dependable.constantize : dependable
60
+ end
61
+
62
+ def resolve_dependable(dependable, optional)
63
+ raise DependencyMissing, dependable unless optional || key?(dependable)
64
+
65
+ self[dependable]
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'realm/container'
4
+
5
+ module Realm
6
+ class Context
7
+ include Enumerable
8
+
9
+ def initialize(*containers)
10
+ @containers = containers.map { |c| Container[c] }
11
+ end
12
+
13
+ def [](name)
14
+ @containers.each do |container|
15
+ return container[name] if container.key?(name)
16
+ end
17
+ nil
18
+ end
19
+
20
+ def key?(name)
21
+ @containers.any? { |container| container.key?(name) }
22
+ end
23
+
24
+ def merge(container_like)
25
+ container_like.blank? ? self : self.class.new(container_like, *@containers)
26
+ end
27
+
28
+ def each(&block)
29
+ @containers.each { |container| container.each(&block) }
30
+ end
31
+
32
+ # Just for testing
33
+ def override!(container)
34
+ @containers.prepend(container)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/string'
4
+
5
+ module Realm
6
+ class Dependency
7
+ attr_reader :dependable, :name
8
+
9
+ def initialize(dependable, as: nil, optional: false, lazy: false) # rubocop:disable Naming/MethodParameterName
10
+ @dependable = dependable
11
+ @name = as || dependable.to_s.demodulize.underscore.to_sym
12
+ @optional = optional
13
+ @lazy = lazy
14
+ end
15
+
16
+ def optional?
17
+ @optional
18
+ end
19
+
20
+ def lazy?
21
+ @lazy
22
+ end
23
+ end
24
+ end