Skip to content

Commit cba7574

Browse files
authored
Refactor setup/finalization (#637)
Refactor setup/configuration/finalization code - Use components directly in the loader - Use components when finalizing schemas - Use components when finalizing relations - Use components when finalizing commands - Use components when finalizing mappers - Remove usage for *_classes methods - Components => Components::Registry - Introduce Components::Relation - Drastically simplify handling of environment and notifications - Remove unused environment config from finalize - Remove unused repo_adapter - Use configuration in Finalize instead of options - Remove redundant configuration.finalize - Move setting default command registry to relation class method [changelog] added: "New component API that replaces internal implementation of the finalization code and makes it possible to extend ROM with arbitrary component types (see #637) (@solnic)"
1 parent 7255136 commit cba7574

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

64 files changed

+803
-614
lines changed

lib/rom/command_registry.rb

+7
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,13 @@ def set_mappers(mappers)
100100
options[:mappers] = @mappers = mappers
101101
end
102102

103+
# @api private
104+
def add(key, command)
105+
raise CommandAlreadyDefinedError, "+#{key}+ is already defined" if key?(key)
106+
107+
elements[key] = command
108+
end
109+
103110
private
104111

105112
# Allow checking if a certain command is available using dot-notation

lib/rom/compat/setup.rb

+18-3
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,25 @@ class Setup
2121
# @api public
2222
def auto_registration(directory, **options)
2323
auto_registration = AutoRegistration.new(directory, inflector: inflector, **options)
24-
auto_registration.relations.map { |r| register_relation(r) }
25-
auto_registration.commands.map { |r| register_command(r) }
26-
auto_registration.mappers.map { |r| register_mapper(r) }
24+
auto_registration.relations.each { |r| register_relation(r) }
25+
auto_registration.commands.each { |r| register_command(r) }
26+
auto_registration.mappers.each { |r| register_mapper(r) }
2727
self
2828
end
29+
30+
# @api public
31+
def relation_classes
32+
components.relations.map(&:constant)
33+
end
34+
35+
# @api public
36+
def command_classes
37+
components.commands.map(&:constant)
38+
end
39+
40+
# @api public
41+
def mapper_classes
42+
components.mappers.map(&:constant)
43+
end
2944
end
3045
end

lib/rom/components.rb

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
# frozen_string_literal: true
2+
3+
require "rom/constants"
4+
require "rom/components/schema"
5+
require "rom/components/command"
6+
require "rom/components/relation"
7+
require "rom/components/mapper"
8+
9+
module ROM
10+
# Setup objects collect component classes during setup/finalization process
11+
#
12+
# @api public
13+
module Components
14+
CORE_TYPES = %i[schemas relations commands mappers].freeze
15+
16+
HANDLERS = {
17+
relations: Relation,
18+
commands: Command,
19+
mappers: Mapper,
20+
schemas: Schema
21+
}.freeze
22+
23+
# @api public
24+
def components
25+
@components ||= Registry.new
26+
end
27+
28+
class Registry
29+
# @api private
30+
attr_reader :types
31+
32+
# @api private
33+
attr_reader :store
34+
35+
# @api private
36+
attr_reader :handlers
37+
38+
# @api private
39+
def initialize(types: CORE_TYPES.dup, handlers: HANDLERS)
40+
@types = types
41+
@store = types.map { |type| [type, EMPTY_ARRAY.dup] }.to_h
42+
@handlers = handlers
43+
end
44+
45+
# @api private
46+
def [](type)
47+
store[type]
48+
end
49+
50+
# @api private
51+
def update(other)
52+
store.each { |type, items| items.concat(other[type]) }
53+
self
54+
end
55+
56+
# @api private
57+
def add(type, **options)
58+
component = handlers.fetch(type).new(**options)
59+
60+
# TODO: this needs a nicer abstraction
61+
# TODO: respond_to? is only needed because auto_register specs use POROs :(
62+
update(component.constant.components) if component.constant.respond_to?(:components)
63+
64+
store[type] << component
65+
66+
component
67+
end
68+
69+
CORE_TYPES.each do |type|
70+
define_method(type) do |**opts|
71+
all = self[type]
72+
return all if opts.empty?
73+
all.select { |el| opts.all? { |key, value| el.public_send(key).eql?(value) } }
74+
end
75+
end
76+
end
77+
end
78+
end

lib/rom/components/command.rb

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "core"
4+
5+
module ROM
6+
module Components
7+
# @api public
8+
class Command < Core
9+
id :command
10+
11+
# Registry id
12+
#
13+
# @return [Symbol]
14+
#
15+
# @api public
16+
def id
17+
constant.register_as || constant.default_name
18+
end
19+
20+
# @return [Symbol]
21+
#
22+
# @api public
23+
def relation_id
24+
constant.relation
25+
end
26+
27+
# @api public
28+
def build(relation:)
29+
trigger(
30+
"commands.class.before_build",
31+
command: constant,
32+
gateway: gateways[relation.gateway],
33+
dataset: relation.dataset,
34+
adapter: adapter
35+
)
36+
37+
constant.extend_for_relation(relation) if constant.restrictable
38+
39+
constant.build(relation)
40+
end
41+
end
42+
end
43+
end

lib/rom/components/core.rb

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
# frozen_string_literal: true
2+
3+
require "dry/effects"
4+
require "dry/core/class_attributes"
5+
require "dry/core/memoizable"
6+
7+
require "rom/constants"
8+
require "rom/initializer"
9+
require "rom/types"
10+
11+
module ROM
12+
module Components
13+
# Abstract component class
14+
#
15+
# @api public
16+
class Core
17+
extend Initializer
18+
extend Dry::Core::ClassAttributes
19+
20+
include Dry::Core::Memoizable
21+
include Dry::Effects.Reader(:configuration)
22+
23+
defines :id
24+
25+
# @!attribute [r] constant
26+
# @return [Class] Component's target class
27+
# @api public
28+
option :constant, optional: true, type: Types.Interface(:new)
29+
alias_method :relation, :constant
30+
31+
# This method is meant to return a run-time component instance
32+
#
33+
# @api public
34+
def build(**)
35+
raise NotImplementedError
36+
end
37+
38+
# @api public
39+
def trigger(event, payload)
40+
notifications.trigger("configuration.#{event}", payload)
41+
end
42+
43+
# @api public
44+
def relations
45+
configuration.relations
46+
end
47+
48+
# @api public
49+
def command_compiler
50+
configuration.command_compiler
51+
end
52+
53+
# @api public
54+
def notifications
55+
configuration.notifications
56+
end
57+
58+
# @api public
59+
def gateways
60+
configuration.gateways
61+
end
62+
63+
# @api public
64+
def components
65+
configuration.components
66+
end
67+
68+
# @api public
69+
def adapter
70+
relation.adapter or raise(
71+
MissingAdapterIdentifierError, "+#{constant}+ is missing the adapter identifier"
72+
)
73+
end
74+
75+
# @api public
76+
def apply_plugins
77+
plugins.each do |plugin|
78+
plugin.apply_to(constant)
79+
end
80+
end
81+
82+
# @api public
83+
memoize def plugins
84+
configuration.plugins.select { |plugin| plugin.type == self.class.id }
85+
end
86+
87+
# @api public
88+
memoize def plugin_options
89+
plugins.map(&:config).map(&:to_hash).reduce(:merge) || EMPTY_HASH
90+
end
91+
92+
private
93+
94+
# @api private
95+
def gateway
96+
gateways.fetch(gateway_name) do
97+
raise "+#{gateway_name.inspect}+ gateway not found for #{constant}"
98+
end
99+
end
100+
101+
# @api private
102+
def gateway_name
103+
relation.gateway
104+
end
105+
end
106+
end
107+
end

lib/rom/components/mapper.rb

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "core"
4+
5+
module ROM
6+
module Components
7+
# @api public
8+
class Mapper < Core
9+
id :mapper
10+
11+
# @!attribute [r] id
12+
# @return [Symbol] Registry key
13+
# @api public
14+
option :id, type: Types.Instance(Symbol), optional: true, reader: false
15+
16+
# @!attribute [r] base_relation
17+
# @return [Symbol] The base relation identifier
18+
# @api public
19+
option :base_relation, type: Types.Instance(Symbol), optional: true, reader: false
20+
21+
# @!attribute [r] object
22+
# @return [Class] Pre-initialized object that should be used instead of the constant
23+
# @api public
24+
option :object, optional: true
25+
26+
# Relation registry id
27+
#
28+
# @return [Symbol]
29+
#
30+
# @api public
31+
def relation_id
32+
options[:base_relation] || constant.base_relation
33+
end
34+
35+
# Registry key
36+
#
37+
# @return [Symbol]
38+
#
39+
# @api public
40+
def id
41+
options[:id] || constant.id
42+
end
43+
44+
# Default container key
45+
#
46+
# @return [String]
47+
#
48+
# @api public
49+
def key
50+
"mappers.#{relation_id}.#{id}"
51+
end
52+
53+
# @api public
54+
def build
55+
object || constant.build
56+
end
57+
end
58+
end
59+
end

0 commit comments

Comments
 (0)