rom-core 4.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/CHANGELOG.md +603 -0
- data/LICENSE +20 -0
- data/README.md +18 -0
- data/lib/rom-core.rb +1 -0
- data/lib/rom/array_dataset.rb +44 -0
- data/lib/rom/association_set.rb +16 -0
- data/lib/rom/associations/abstract.rb +135 -0
- data/lib/rom/associations/definitions.rb +5 -0
- data/lib/rom/associations/definitions/abstract.rb +116 -0
- data/lib/rom/associations/definitions/many_to_many.rb +24 -0
- data/lib/rom/associations/definitions/many_to_one.rb +11 -0
- data/lib/rom/associations/definitions/one_to_many.rb +11 -0
- data/lib/rom/associations/definitions/one_to_one.rb +11 -0
- data/lib/rom/associations/definitions/one_to_one_through.rb +11 -0
- data/lib/rom/associations/many_to_many.rb +81 -0
- data/lib/rom/associations/many_to_one.rb +37 -0
- data/lib/rom/associations/one_to_many.rb +37 -0
- data/lib/rom/associations/one_to_one.rb +8 -0
- data/lib/rom/associations/one_to_one_through.rb +8 -0
- data/lib/rom/associations/through_identifier.rb +39 -0
- data/lib/rom/auto_curry.rb +55 -0
- data/lib/rom/cache.rb +46 -0
- data/lib/rom/command.rb +488 -0
- data/lib/rom/command_compiler.rb +239 -0
- data/lib/rom/command_proxy.rb +24 -0
- data/lib/rom/command_registry.rb +141 -0
- data/lib/rom/commands.rb +3 -0
- data/lib/rom/commands/class_interface.rb +270 -0
- data/lib/rom/commands/composite.rb +53 -0
- data/lib/rom/commands/create.rb +13 -0
- data/lib/rom/commands/delete.rb +14 -0
- data/lib/rom/commands/graph.rb +88 -0
- data/lib/rom/commands/graph/class_interface.rb +62 -0
- data/lib/rom/commands/graph/input_evaluator.rb +62 -0
- data/lib/rom/commands/lazy.rb +99 -0
- data/lib/rom/commands/lazy/create.rb +23 -0
- data/lib/rom/commands/lazy/delete.rb +27 -0
- data/lib/rom/commands/lazy/update.rb +34 -0
- data/lib/rom/commands/result.rb +96 -0
- data/lib/rom/commands/update.rb +14 -0
- data/lib/rom/configuration.rb +114 -0
- data/lib/rom/configuration_dsl.rb +87 -0
- data/lib/rom/configuration_dsl/command.rb +41 -0
- data/lib/rom/configuration_dsl/command_dsl.rb +35 -0
- data/lib/rom/configuration_dsl/relation.rb +26 -0
- data/lib/rom/configuration_plugin.rb +17 -0
- data/lib/rom/constants.rb +64 -0
- data/lib/rom/container.rb +147 -0
- data/lib/rom/core.rb +46 -0
- data/lib/rom/create_container.rb +60 -0
- data/lib/rom/data_proxy.rb +94 -0
- data/lib/rom/enumerable_dataset.rb +68 -0
- data/lib/rom/environment.rb +70 -0
- data/lib/rom/gateway.rb +184 -0
- data/lib/rom/global.rb +58 -0
- data/lib/rom/global/plugin_dsl.rb +47 -0
- data/lib/rom/initializer.rb +64 -0
- data/lib/rom/lint/enumerable_dataset.rb +54 -0
- data/lib/rom/lint/gateway.rb +120 -0
- data/lib/rom/lint/linter.rb +78 -0
- data/lib/rom/lint/spec.rb +20 -0
- data/lib/rom/lint/test.rb +98 -0
- data/lib/rom/mapper_registry.rb +24 -0
- data/lib/rom/memory.rb +4 -0
- data/lib/rom/memory/associations.rb +4 -0
- data/lib/rom/memory/associations/many_to_many.rb +10 -0
- data/lib/rom/memory/associations/many_to_one.rb +10 -0
- data/lib/rom/memory/associations/one_to_many.rb +10 -0
- data/lib/rom/memory/associations/one_to_one.rb +10 -0
- data/lib/rom/memory/commands.rb +56 -0
- data/lib/rom/memory/dataset.rb +97 -0
- data/lib/rom/memory/gateway.rb +64 -0
- data/lib/rom/memory/relation.rb +62 -0
- data/lib/rom/memory/schema.rb +23 -0
- data/lib/rom/memory/storage.rb +59 -0
- data/lib/rom/memory/types.rb +9 -0
- data/lib/rom/pipeline.rb +105 -0
- data/lib/rom/plugin.rb +25 -0
- data/lib/rom/plugin_base.rb +45 -0
- data/lib/rom/plugin_registry.rb +197 -0
- data/lib/rom/plugins/command/schema.rb +37 -0
- data/lib/rom/plugins/configuration/configuration_dsl.rb +21 -0
- data/lib/rom/plugins/relation/instrumentation.rb +51 -0
- data/lib/rom/plugins/relation/registry_reader.rb +44 -0
- data/lib/rom/plugins/schema/timestamps.rb +58 -0
- data/lib/rom/registry.rb +71 -0
- data/lib/rom/relation.rb +548 -0
- data/lib/rom/relation/class_interface.rb +282 -0
- data/lib/rom/relation/commands.rb +23 -0
- data/lib/rom/relation/composite.rb +46 -0
- data/lib/rom/relation/curried.rb +103 -0
- data/lib/rom/relation/graph.rb +197 -0
- data/lib/rom/relation/loaded.rb +127 -0
- data/lib/rom/relation/materializable.rb +66 -0
- data/lib/rom/relation/name.rb +111 -0
- data/lib/rom/relation/view_dsl.rb +64 -0
- data/lib/rom/relation/wrap.rb +83 -0
- data/lib/rom/relation_registry.rb +10 -0
- data/lib/rom/schema.rb +437 -0
- data/lib/rom/schema/associations_dsl.rb +195 -0
- data/lib/rom/schema/attribute.rb +419 -0
- data/lib/rom/schema/dsl.rb +164 -0
- data/lib/rom/schema/inferrer.rb +66 -0
- data/lib/rom/schema_plugin.rb +27 -0
- data/lib/rom/setup.rb +68 -0
- data/lib/rom/setup/auto_registration.rb +74 -0
- data/lib/rom/setup/auto_registration_strategies/base.rb +16 -0
- data/lib/rom/setup/auto_registration_strategies/custom_namespace.rb +63 -0
- data/lib/rom/setup/auto_registration_strategies/no_namespace.rb +20 -0
- data/lib/rom/setup/auto_registration_strategies/with_namespace.rb +18 -0
- data/lib/rom/setup/finalize.rb +103 -0
- data/lib/rom/setup/finalize/finalize_commands.rb +60 -0
- data/lib/rom/setup/finalize/finalize_mappers.rb +56 -0
- data/lib/rom/setup/finalize/finalize_relations.rb +135 -0
- data/lib/rom/support/configurable.rb +85 -0
- data/lib/rom/support/memoizable.rb +58 -0
- data/lib/rom/support/notifications.rb +103 -0
- data/lib/rom/transaction.rb +24 -0
- data/lib/rom/types.rb +26 -0
- data/lib/rom/version.rb +5 -0
- metadata +289 -0
@@ -0,0 +1,239 @@
|
|
1
|
+
require 'dry/core/inflector'
|
2
|
+
require 'dry/core/cache'
|
3
|
+
|
4
|
+
require 'rom/initializer'
|
5
|
+
require 'rom/commands'
|
6
|
+
require 'rom/command_proxy'
|
7
|
+
|
8
|
+
module ROM
|
9
|
+
# Builds commands for relations.
|
10
|
+
#
|
11
|
+
# This class is used by repositories to automatically create commands for
|
12
|
+
# their relations. This is used both by `Repository#command` method and
|
13
|
+
# `commands` repository class macros.
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
class CommandCompiler
|
17
|
+
extend Dry::Core::Cache
|
18
|
+
extend Initializer
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
def self.registry
|
22
|
+
@__registry__ ||= Hash.new { |h, k| h[k] = {} }
|
23
|
+
end
|
24
|
+
|
25
|
+
# @!attribute [r] gateways
|
26
|
+
# @return [ROM::Registry]
|
27
|
+
param :gateways
|
28
|
+
|
29
|
+
# @!attribute [r] relations
|
30
|
+
# @return [ROM::RelationRegistry]
|
31
|
+
param :relations
|
32
|
+
|
33
|
+
# @!attribute [r] relations
|
34
|
+
# @return [ROM::Registry]
|
35
|
+
param :commands
|
36
|
+
|
37
|
+
# @!attribute [r] notifications
|
38
|
+
# @return [Notifications::EventBus] Configuration notifications event bus
|
39
|
+
param :notifications
|
40
|
+
|
41
|
+
# @!attribute [r] id
|
42
|
+
# @return [Symbol] The command type registry identifier
|
43
|
+
option :id, optional: true
|
44
|
+
|
45
|
+
# @!attribute [r] adapter
|
46
|
+
# @return [Symbol] The adapter identifier ie :sql or :http
|
47
|
+
option :adapter, optional: true
|
48
|
+
|
49
|
+
# @!attribute [r] registry
|
50
|
+
# @return [Hash] local registry where commands will be stored during compilation
|
51
|
+
option :registry, optional: true, default: -> { self.class.registry }
|
52
|
+
|
53
|
+
# @!attribute [r] plugins
|
54
|
+
# @return [Array<Symbol>] a list of optional plugins that will be enabled for commands
|
55
|
+
option :plugins, optional: true, default: -> { EMPTY_ARRAY }
|
56
|
+
|
57
|
+
# @!attribute [r] meta
|
58
|
+
# @return [Array<Symbol>] Meta data for a command
|
59
|
+
option :meta, optional: true
|
60
|
+
|
61
|
+
# Return a specific command type for a given adapter and relation AST
|
62
|
+
#
|
63
|
+
# This class holds its own registry where all generated commands are being
|
64
|
+
# stored
|
65
|
+
#
|
66
|
+
# CommandProxy is returned for complex command graphs as they expect root
|
67
|
+
# relation name to be present in the input, which we don't want to have
|
68
|
+
# in repositories. It might be worth looking into removing this requirement
|
69
|
+
# from rom core Command::Graph API.
|
70
|
+
#
|
71
|
+
# @overload [](type, adapter, ast, plugins, meta)
|
72
|
+
# @param type [Symbol] The type of command
|
73
|
+
# @param adapter [Symbol] The adapter identifier
|
74
|
+
# @param ast [Array] The AST representation of a relation
|
75
|
+
# @param plugins [Array<Symbol>] A list of optional command plugins that should be used
|
76
|
+
# @param meta [Hash] Meta data for a command
|
77
|
+
#
|
78
|
+
# @return [Command, CommandProxy]
|
79
|
+
#
|
80
|
+
# @api private
|
81
|
+
def call(*args)
|
82
|
+
fetch_or_store(args.hash) do
|
83
|
+
type, adapter, ast, plugins, meta = args
|
84
|
+
|
85
|
+
compiler = with(
|
86
|
+
id: type,
|
87
|
+
adapter: adapter,
|
88
|
+
plugins: Array(plugins),
|
89
|
+
meta: meta
|
90
|
+
)
|
91
|
+
|
92
|
+
graph_opts = compiler.visit(ast)
|
93
|
+
command = ROM::Commands::Graph.build(registry, graph_opts)
|
94
|
+
|
95
|
+
if command.graph?
|
96
|
+
CommandProxy.new(command)
|
97
|
+
elsif command.lazy?
|
98
|
+
command.unwrap
|
99
|
+
else
|
100
|
+
command
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
alias_method :[], :call
|
105
|
+
|
106
|
+
# @api private
|
107
|
+
def type
|
108
|
+
@_type ||= Commands.const_get(Dry::Core::Inflector.classify(id))[adapter]
|
109
|
+
rescue NameError
|
110
|
+
nil
|
111
|
+
end
|
112
|
+
|
113
|
+
# @api private
|
114
|
+
def visit(ast, *args)
|
115
|
+
name, node = ast
|
116
|
+
__send__(:"visit_#{name}", node, *args)
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
# @api private
|
122
|
+
def visit_relation(node, parent_relation = nil)
|
123
|
+
name, header, meta = node
|
124
|
+
other = header.map { |attr| visit(attr, name) }.compact
|
125
|
+
|
126
|
+
if type
|
127
|
+
register_command(name, type, meta, parent_relation)
|
128
|
+
|
129
|
+
default_mapping =
|
130
|
+
if meta[:combine_type] == :many
|
131
|
+
name
|
132
|
+
else
|
133
|
+
{ Dry::Core::Inflector.singularize(name).to_sym => name }
|
134
|
+
end
|
135
|
+
|
136
|
+
mapping =
|
137
|
+
if parent_relation
|
138
|
+
associations = relations[parent_relation].associations
|
139
|
+
|
140
|
+
assoc = associations[meta[:combine_name]]
|
141
|
+
|
142
|
+
if assoc
|
143
|
+
{ assoc.key => assoc.target.name.to_sym }
|
144
|
+
else
|
145
|
+
default_mapping
|
146
|
+
end
|
147
|
+
else
|
148
|
+
default_mapping
|
149
|
+
end
|
150
|
+
|
151
|
+
if other.size > 0
|
152
|
+
[mapping, [type, other]]
|
153
|
+
else
|
154
|
+
[mapping, type]
|
155
|
+
end
|
156
|
+
else
|
157
|
+
registry[name][id] = commands[name][id]
|
158
|
+
[name, id]
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
# @api private
|
163
|
+
def visit_attribute(*args)
|
164
|
+
nil
|
165
|
+
end
|
166
|
+
|
167
|
+
# Build a command object for a specific relation
|
168
|
+
#
|
169
|
+
# The command will be prepared for handling associations if it's a combined
|
170
|
+
# relation. Additional plugins will be enabled if they are configured for
|
171
|
+
# this compiler.
|
172
|
+
#
|
173
|
+
# @param [Symbol] rel_name A relation identifier from the container registry
|
174
|
+
# @param [Symbol] type The command type
|
175
|
+
# @param [Hash] meta Meta information from relation AST
|
176
|
+
# @param [Symbol] parent_relation Optional parent relation identifier
|
177
|
+
#
|
178
|
+
# @return [ROM::Command]
|
179
|
+
#
|
180
|
+
# @api private
|
181
|
+
def register_command(rel_name, type, meta, parent_relation = nil)
|
182
|
+
relation = relations[rel_name]
|
183
|
+
|
184
|
+
type.create_class(rel_name, type) do |klass|
|
185
|
+
klass.result(meta.fetch(:combine_type, result))
|
186
|
+
|
187
|
+
if meta[:combine_type]
|
188
|
+
setup_associates(klass, relation, meta, parent_relation)
|
189
|
+
end
|
190
|
+
|
191
|
+
plugins.each do |plugin|
|
192
|
+
klass.use(plugin)
|
193
|
+
end
|
194
|
+
|
195
|
+
gateway = gateways[relation.gateway]
|
196
|
+
|
197
|
+
notifications.trigger(
|
198
|
+
'configuration.commands.class.before_build',
|
199
|
+
command: klass, gateway: gateway, dataset: relation.dataset
|
200
|
+
)
|
201
|
+
|
202
|
+
klass.extend_for_relation(relation) if klass.restrictable
|
203
|
+
|
204
|
+
registry[rel_name][type] = klass.build(relation, input: relation.input_schema)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Return default result type
|
209
|
+
#
|
210
|
+
# @return [Symbol]
|
211
|
+
#
|
212
|
+
# @api private
|
213
|
+
def result
|
214
|
+
meta.fetch(:result, :one)
|
215
|
+
end
|
216
|
+
|
217
|
+
# Sets up `associates` plugin for a given command class and relation
|
218
|
+
#
|
219
|
+
# @param [Class] klass The command class
|
220
|
+
# @param [Relation] relation The relation for the command
|
221
|
+
#
|
222
|
+
# @api private
|
223
|
+
def setup_associates(klass, relation, meta, parent_relation)
|
224
|
+
assoc_name =
|
225
|
+
if relation.associations.key?(parent_relation)
|
226
|
+
parent_relation
|
227
|
+
else
|
228
|
+
singular_name = Dry::Core::Inflector.singularize(parent_relation).to_sym
|
229
|
+
singular_name if relation.associations.key?(singular_name)
|
230
|
+
end
|
231
|
+
|
232
|
+
if assoc_name
|
233
|
+
klass.associates(assoc_name)
|
234
|
+
else
|
235
|
+
klass.associates(parent_relation)
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'dry/core/inflector'
|
2
|
+
|
3
|
+
module ROM
|
4
|
+
# TODO: look into making command graphs work without the root key in the input
|
5
|
+
# so that we can get rid of this wrapper
|
6
|
+
#
|
7
|
+
# @api private
|
8
|
+
class CommandProxy
|
9
|
+
attr_reader :command, :root
|
10
|
+
|
11
|
+
def initialize(command)
|
12
|
+
@command = command
|
13
|
+
@root = Dry::Core::Inflector.singularize(command.name.relation).to_sym
|
14
|
+
end
|
15
|
+
|
16
|
+
def call(input)
|
17
|
+
command.call(root => input)
|
18
|
+
end
|
19
|
+
|
20
|
+
def >>(other)
|
21
|
+
self.class.new(command >> other)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,141 @@
|
|
1
|
+
require 'concurrent/map'
|
2
|
+
|
3
|
+
require 'rom/constants'
|
4
|
+
require 'rom/registry'
|
5
|
+
require 'rom/commands/result'
|
6
|
+
|
7
|
+
module ROM
|
8
|
+
# Specialized registry class for commands
|
9
|
+
#
|
10
|
+
# @api public
|
11
|
+
class CommandRegistry < Registry
|
12
|
+
include Commands
|
13
|
+
|
14
|
+
# Internal command registry
|
15
|
+
#
|
16
|
+
# @return [Registry]
|
17
|
+
#
|
18
|
+
# @api private
|
19
|
+
param :elements
|
20
|
+
|
21
|
+
# Name of the relation from which commands are under
|
22
|
+
#
|
23
|
+
# @return [String]
|
24
|
+
#
|
25
|
+
# @api private
|
26
|
+
option :relation_name
|
27
|
+
|
28
|
+
option :mappers, optional: true
|
29
|
+
|
30
|
+
option :mapper, optional: true
|
31
|
+
|
32
|
+
option :compiler, optional: true
|
33
|
+
|
34
|
+
def self.element_not_found_error
|
35
|
+
CommandNotFoundError
|
36
|
+
end
|
37
|
+
|
38
|
+
# Try to execute a command in a block
|
39
|
+
#
|
40
|
+
# @yield [command] Passes command to the block
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
#
|
44
|
+
# rom.commands[:users].try { create(name: 'Jane') }
|
45
|
+
# rom.commands[:users].try { update(:by_id, 1).call(name: 'Jane Doe') }
|
46
|
+
# rom.commands[:users].try { delete(:by_id, 1) }
|
47
|
+
#
|
48
|
+
# @return [Commands::Result]
|
49
|
+
#
|
50
|
+
# @api public
|
51
|
+
def try(&block)
|
52
|
+
response = block.call
|
53
|
+
|
54
|
+
if response.is_a?(Command) || response.is_a?(Composite)
|
55
|
+
try { response.call }
|
56
|
+
else
|
57
|
+
Result::Success.new(response)
|
58
|
+
end
|
59
|
+
rescue CommandError => e
|
60
|
+
Result::Failure.new(e)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Return a command from the registry
|
64
|
+
#
|
65
|
+
# If mapper is set command will be turned into a composite command with
|
66
|
+
# auto-mapping
|
67
|
+
#
|
68
|
+
# @example
|
69
|
+
# create_user = rom.commands[:users][:create]
|
70
|
+
# create_user[name: 'Jane']
|
71
|
+
#
|
72
|
+
# # with mapping, assuming :entity mapper is registered for :users relation
|
73
|
+
# create_user = rom.commands[:users].map_with(:entity)[:create]
|
74
|
+
# create_user[name: 'Jane'] # => result is send through :entity mapper
|
75
|
+
#
|
76
|
+
# @param [Symbol] name The name of a registered command
|
77
|
+
#
|
78
|
+
# @return [Command,Command::Composite]
|
79
|
+
#
|
80
|
+
# @api public
|
81
|
+
def [](*args)
|
82
|
+
if args.size.equal?(1)
|
83
|
+
command = super
|
84
|
+
mapper = options[:mapper]
|
85
|
+
|
86
|
+
if mapper
|
87
|
+
command.curry >> mapper
|
88
|
+
else
|
89
|
+
command
|
90
|
+
end
|
91
|
+
else
|
92
|
+
cache.fetch_or_store(args.hash) { compiler.(*args) }
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# Specify a mapper that should be used for commands from this registry
|
97
|
+
#
|
98
|
+
# @example
|
99
|
+
# entity_commands = rom.commands[:users].map_with(:entity)
|
100
|
+
#
|
101
|
+
#
|
102
|
+
# @param [Symbol] mapper_name The name of a registered mapper
|
103
|
+
#
|
104
|
+
# @return [CommandRegistry]
|
105
|
+
#
|
106
|
+
# @api public
|
107
|
+
def map_with(mapper_name)
|
108
|
+
with(mapper: mappers[mapper_name])
|
109
|
+
end
|
110
|
+
|
111
|
+
# @api private
|
112
|
+
def set_compiler(compiler)
|
113
|
+
options[:compiler] = @compiler = compiler
|
114
|
+
end
|
115
|
+
|
116
|
+
# @api private
|
117
|
+
def set_mappers(mappers)
|
118
|
+
options[:mappers] = @mappers = mappers
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
|
123
|
+
# Allow checking if a certain command is available using dot-notation
|
124
|
+
#
|
125
|
+
# @api private
|
126
|
+
def respond_to_missing?(name, include_private = false)
|
127
|
+
key?(name) || super
|
128
|
+
end
|
129
|
+
|
130
|
+
# Allow retrieving commands using dot-notation
|
131
|
+
#
|
132
|
+
# @api private
|
133
|
+
def method_missing(name, *)
|
134
|
+
if key?(name)
|
135
|
+
self[name]
|
136
|
+
else
|
137
|
+
super
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
data/lib/rom/commands.rb
ADDED
@@ -0,0 +1,270 @@
|
|
1
|
+
require 'dry/core/class_builder'
|
2
|
+
require 'dry/core/inflector'
|
3
|
+
|
4
|
+
module ROM
|
5
|
+
# Base command class with factory class-level interface and setup-related logic
|
6
|
+
#
|
7
|
+
# @private
|
8
|
+
class Command
|
9
|
+
module ClassInterface
|
10
|
+
# This hook sets up default class state
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
def inherited(klass)
|
14
|
+
super
|
15
|
+
klass.instance_variable_set(:'@before', before.dup)
|
16
|
+
klass.instance_variable_set(:'@after', after.dup)
|
17
|
+
end
|
18
|
+
|
19
|
+
# Sets up the base class
|
20
|
+
#
|
21
|
+
# @api private
|
22
|
+
def self.extended(klass)
|
23
|
+
super
|
24
|
+
klass.set_hooks(:before, [])
|
25
|
+
klass.set_hooks(:after, [])
|
26
|
+
end
|
27
|
+
|
28
|
+
# Return adapter specific sub-class based on the adapter identifier
|
29
|
+
#
|
30
|
+
# This is a syntax sugar to make things consistent
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# ROM::Commands::Create[:memory]
|
34
|
+
# # => ROM::Memory::Commands::Create
|
35
|
+
#
|
36
|
+
# @param [Symbol] adapter identifier
|
37
|
+
#
|
38
|
+
# @return [Class]
|
39
|
+
#
|
40
|
+
# @api public
|
41
|
+
def [](adapter)
|
42
|
+
adapter_namespace(adapter).const_get(Dry::Core::Inflector.demodulize(name))
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return namespaces that contains command subclasses of a specific adapter
|
46
|
+
#
|
47
|
+
# @param [Symbol] adapter identifier
|
48
|
+
#
|
49
|
+
# @return [Module]
|
50
|
+
#
|
51
|
+
# @api private
|
52
|
+
def adapter_namespace(adapter)
|
53
|
+
ROM.adapters.fetch(adapter).const_get(:Commands)
|
54
|
+
rescue KeyError
|
55
|
+
raise AdapterNotPresentError.new(adapter, :relation)
|
56
|
+
end
|
57
|
+
|
58
|
+
# Build a command class for a specific relation with options
|
59
|
+
#
|
60
|
+
# @example
|
61
|
+
# class CreateUser < ROM::Commands::Create[:memory]
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# command = CreateUser.build(rom.relations[:users])
|
65
|
+
#
|
66
|
+
# @param [Relation] relation
|
67
|
+
# @param [Hash] options
|
68
|
+
#
|
69
|
+
# @return [Command]
|
70
|
+
#
|
71
|
+
# @api public
|
72
|
+
def build(relation, options = EMPTY_HASH)
|
73
|
+
new(relation, self.options.merge(options))
|
74
|
+
end
|
75
|
+
|
76
|
+
# Create a command class with a specific type
|
77
|
+
#
|
78
|
+
# @param [Symbol] command name
|
79
|
+
# @param [Class] parent class
|
80
|
+
#
|
81
|
+
# @yield [Class] create class
|
82
|
+
#
|
83
|
+
# @return [Class, Object] return result of the block if it was provided
|
84
|
+
#
|
85
|
+
# @api public
|
86
|
+
def create_class(name, type, &block)
|
87
|
+
klass = Dry::Core::ClassBuilder
|
88
|
+
.new(name: "#{Dry::Core::Inflector.classify(type)}[:#{name}]", parent: type)
|
89
|
+
.call
|
90
|
+
|
91
|
+
if block
|
92
|
+
yield(klass)
|
93
|
+
else
|
94
|
+
klass
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Use a configured plugin in this relation
|
99
|
+
#
|
100
|
+
# @example
|
101
|
+
# class CreateUser < ROM::Commands::Create[:memory]
|
102
|
+
# use :pagintion
|
103
|
+
#
|
104
|
+
# per_page 30
|
105
|
+
# end
|
106
|
+
#
|
107
|
+
# @param [Symbol] plugin
|
108
|
+
# @param [Hash] options
|
109
|
+
# @option options [Symbol] :adapter (:default) first adapter to check for plugin
|
110
|
+
#
|
111
|
+
# @api public
|
112
|
+
def use(plugin, _options = EMPTY_HASH)
|
113
|
+
ROM.plugin_registry.commands.fetch(plugin, adapter).apply_to(self)
|
114
|
+
end
|
115
|
+
|
116
|
+
# Extend a command class with relation view methods
|
117
|
+
#
|
118
|
+
# @param [Relation]
|
119
|
+
#
|
120
|
+
# @return [Class]
|
121
|
+
#
|
122
|
+
# @api public
|
123
|
+
def extend_for_relation(relation)
|
124
|
+
include(relation_methods_mod(relation.class))
|
125
|
+
end
|
126
|
+
|
127
|
+
# Set before-execute hooks
|
128
|
+
#
|
129
|
+
# @overload before(hook)
|
130
|
+
# Set an before hook as a method name
|
131
|
+
#
|
132
|
+
# @example
|
133
|
+
# class CreateUser < ROM::Commands::Create[:sql]
|
134
|
+
# relation :users
|
135
|
+
# register_as :create
|
136
|
+
#
|
137
|
+
# before :my_hook
|
138
|
+
#
|
139
|
+
# def my_hook(tuple, *)
|
140
|
+
# puts "hook called#
|
141
|
+
# end
|
142
|
+
# end
|
143
|
+
#
|
144
|
+
# @overload before(hook_opts)
|
145
|
+
# Set an before hook as a method name with arguments
|
146
|
+
#
|
147
|
+
# @example
|
148
|
+
# class CreateUser < ROM::Commands::Create[:sql]
|
149
|
+
# relation :users
|
150
|
+
# register_as :create
|
151
|
+
#
|
152
|
+
# before my_hook: { arg1: 1, arg1: 2 }
|
153
|
+
#
|
154
|
+
# def my_hook(tuple, arg1:, arg2:)
|
155
|
+
# puts "hook called with args: #{arg1} and #{arg2}"
|
156
|
+
# end
|
157
|
+
# end
|
158
|
+
#
|
159
|
+
# @param [Hash<Symbol=>Hash>] hook Options with method name and pre-set args
|
160
|
+
#
|
161
|
+
# @return [Array<Hash, Symbol>] A list of all configured before hooks
|
162
|
+
#
|
163
|
+
# @api public
|
164
|
+
def before(*hooks)
|
165
|
+
if hooks.size > 0
|
166
|
+
set_hooks(:before, hooks)
|
167
|
+
else
|
168
|
+
@before
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
# Set after-execute hooks
|
173
|
+
#
|
174
|
+
# @overload after(hook)
|
175
|
+
# Set an after hook as a method name
|
176
|
+
#
|
177
|
+
# @example
|
178
|
+
# class CreateUser < ROM::Commands::Create[:sql]
|
179
|
+
# relation :users
|
180
|
+
# register_as :create
|
181
|
+
#
|
182
|
+
# after :my_hook
|
183
|
+
#
|
184
|
+
# def my_hook(tuple, *)
|
185
|
+
# puts "hook called#
|
186
|
+
# end
|
187
|
+
# end
|
188
|
+
#
|
189
|
+
# @overload after(hook_opts)
|
190
|
+
# Set an after hook as a method name with arguments
|
191
|
+
#
|
192
|
+
# @example
|
193
|
+
# class CreateUser < ROM::Commands::Create[:sql]
|
194
|
+
# relation :users
|
195
|
+
# register_as :create
|
196
|
+
#
|
197
|
+
# after my_hook: { arg1: 1, arg1: 2 }
|
198
|
+
#
|
199
|
+
# def my_hook(tuple, arg1:, arg2:)
|
200
|
+
# puts "hook called with args: #{arg1} and #{arg2}"
|
201
|
+
# end
|
202
|
+
# end
|
203
|
+
#
|
204
|
+
# @param [Hash<Symbol=>Hash>] hook Options with method name and pre-set args
|
205
|
+
#
|
206
|
+
# @return [Array<Hash, Symbol>] A list of all configured after hooks
|
207
|
+
#
|
208
|
+
# @api public
|
209
|
+
def after(*hooks)
|
210
|
+
if hooks.size > 0
|
211
|
+
set_hooks(:after, hooks)
|
212
|
+
else
|
213
|
+
@after
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
# Set new or more hooks
|
218
|
+
#
|
219
|
+
# @api private
|
220
|
+
def set_hooks(type, hooks)
|
221
|
+
ivar = :"@#{type}"
|
222
|
+
|
223
|
+
if instance_variable_defined?(ivar)
|
224
|
+
instance_variable_get(ivar).concat(hooks)
|
225
|
+
else
|
226
|
+
instance_variable_set(ivar, hooks)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
# Return default name of the command class based on its name
|
231
|
+
#
|
232
|
+
# During setup phase this is used by defalut as `register_as` option
|
233
|
+
#
|
234
|
+
# @return [Symbol]
|
235
|
+
#
|
236
|
+
# @api private
|
237
|
+
def default_name
|
238
|
+
Dry::Core::Inflector.underscore(Dry::Core::Inflector.demodulize(name)).to_sym
|
239
|
+
end
|
240
|
+
|
241
|
+
# Return default options based on class macros
|
242
|
+
#
|
243
|
+
# @return [Hash]
|
244
|
+
#
|
245
|
+
# @api private
|
246
|
+
def options
|
247
|
+
{ input: input, result: result, before: before, after: after }
|
248
|
+
end
|
249
|
+
|
250
|
+
# @api private
|
251
|
+
def relation_methods_mod(relation_class)
|
252
|
+
Module.new do
|
253
|
+
relation_class.view_methods.each do |meth|
|
254
|
+
module_eval <<-RUBY, __FILE__, __LINE__ + 1
|
255
|
+
def #{meth}(*args)
|
256
|
+
response = relation.public_send(:#{meth}, *args)
|
257
|
+
|
258
|
+
if response.is_a?(relation.class)
|
259
|
+
new(response)
|
260
|
+
else
|
261
|
+
response
|
262
|
+
end
|
263
|
+
end
|
264
|
+
RUBY
|
265
|
+
end
|
266
|
+
end
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|