rom 5.4.1 → 6.0.0.alpha1
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 +4 -4
- data/CHANGELOG.md +58 -65
- data/LICENSE +1 -1
- data/README.md +7 -6
- data/lib/rom/array_dataset.rb +46 -0
- data/lib/rom/associations/abstract.rb +217 -0
- data/lib/rom/associations/definitions/abstract.rb +150 -0
- data/lib/rom/associations/definitions/many_to_many.rb +29 -0
- data/lib/rom/associations/definitions/many_to_one.rb +14 -0
- data/lib/rom/associations/definitions/one_to_many.rb +14 -0
- data/lib/rom/associations/definitions/one_to_one.rb +14 -0
- data/lib/rom/associations/definitions/one_to_one_through.rb +14 -0
- data/lib/rom/associations/definitions.rb +7 -0
- data/lib/rom/associations/many_to_many.rb +128 -0
- data/lib/rom/associations/many_to_one.rb +65 -0
- data/lib/rom/associations/one_to_many.rb +65 -0
- data/lib/rom/associations/one_to_one.rb +13 -0
- data/lib/rom/associations/one_to_one_through.rb +13 -0
- data/lib/rom/associations/through_identifier.rb +41 -0
- data/lib/rom/attribute.rb +425 -0
- data/lib/rom/auto_curry.rb +70 -0
- data/lib/rom/cache.rb +87 -0
- data/lib/rom/changeset/associated.rb +110 -0
- data/lib/rom/changeset/create.rb +18 -0
- data/lib/rom/changeset/delete.rb +15 -0
- data/lib/rom/changeset/extensions/relation.rb +26 -0
- data/lib/rom/changeset/pipe.rb +81 -0
- data/lib/rom/changeset/pipe_registry.rb +27 -0
- data/lib/rom/changeset/stateful.rb +285 -0
- data/lib/rom/changeset/update.rb +81 -0
- data/lib/rom/changeset.rb +185 -0
- data/lib/rom/command.rb +351 -0
- data/lib/rom/command_compiler.rb +201 -0
- data/lib/rom/command_proxy.rb +36 -0
- data/lib/rom/commands/class_interface.rb +236 -0
- data/lib/rom/commands/composite.rb +55 -0
- data/lib/rom/commands/create.rb +15 -0
- data/lib/rom/commands/delete.rb +16 -0
- data/lib/rom/commands/graph/class_interface.rb +64 -0
- data/lib/rom/commands/graph/input_evaluator.rb +94 -0
- data/lib/rom/commands/graph.rb +88 -0
- data/lib/rom/commands/lazy/create.rb +35 -0
- data/lib/rom/commands/lazy/delete.rb +39 -0
- data/lib/rom/commands/lazy/update.rb +46 -0
- data/lib/rom/commands/lazy.rb +106 -0
- data/lib/rom/commands/update.rb +16 -0
- data/lib/rom/commands.rb +5 -0
- data/lib/rom/compat/auto_registration.rb +115 -0
- data/lib/rom/compat/auto_registration_strategies/base.rb +29 -0
- data/lib/rom/compat/auto_registration_strategies/custom_namespace.rb +84 -0
- data/lib/rom/compat/auto_registration_strategies/no_namespace.rb +33 -0
- data/lib/rom/compat/auto_registration_strategies/with_namespace.rb +29 -0
- data/lib/rom/compat/command.rb +74 -0
- data/lib/rom/compat/components/dsl/schema.rb +130 -0
- data/lib/rom/compat/components.rb +91 -0
- data/lib/rom/compat/global.rb +17 -0
- data/lib/rom/compat/mapper.rb +22 -0
- data/lib/rom/compat/registries.rb +47 -0
- data/lib/rom/compat/relation.rb +40 -0
- data/lib/rom/compat/schema/dsl.rb +260 -0
- data/lib/rom/compat/setting_proxy.rb +44 -0
- data/lib/rom/compat/setup.rb +151 -0
- data/lib/rom/compat/transformer.rb +49 -0
- data/lib/rom/compat.rb +22 -0
- data/lib/rom/components/association.rb +26 -0
- data/lib/rom/components/command.rb +24 -0
- data/lib/rom/components/core.rb +148 -0
- data/lib/rom/components/dataset.rb +60 -0
- data/lib/rom/components/dsl/association.rb +47 -0
- data/lib/rom/components/dsl/command.rb +60 -0
- data/lib/rom/components/dsl/core.rb +126 -0
- data/lib/rom/components/dsl/dataset.rb +33 -0
- data/lib/rom/components/dsl/gateway.rb +14 -0
- data/lib/rom/components/dsl/mapper.rb +70 -0
- data/lib/rom/components/dsl/relation.rb +49 -0
- data/lib/rom/components/dsl/schema.rb +150 -0
- data/lib/rom/components/dsl/view.rb +82 -0
- data/lib/rom/components/dsl.rb +255 -0
- data/lib/rom/components/gateway.rb +50 -0
- data/lib/rom/components/mapper.rb +29 -0
- data/lib/rom/components/provider.rb +160 -0
- data/lib/rom/components/registry.rb +154 -0
- data/lib/rom/components/relation.rb +41 -0
- data/lib/rom/components/schema.rb +61 -0
- data/lib/rom/components/view.rb +55 -0
- data/lib/rom/components.rb +55 -0
- data/lib/rom/configuration_dsl.rb +4 -0
- data/lib/rom/constants.rb +135 -0
- data/lib/rom/container.rb +182 -0
- data/lib/rom/core.rb +125 -0
- data/lib/rom/data_proxy.rb +97 -0
- data/lib/rom/enumerable_dataset.rb +70 -0
- data/lib/rom/gateway.rb +232 -0
- data/lib/rom/global.rb +56 -0
- data/lib/rom/header/attribute.rb +190 -0
- data/lib/rom/header.rb +198 -0
- data/lib/rom/inferrer.rb +55 -0
- data/lib/rom/initializer.rb +80 -0
- data/lib/rom/lint/enumerable_dataset.rb +56 -0
- data/lib/rom/lint/gateway.rb +120 -0
- data/lib/rom/lint/linter.rb +79 -0
- data/lib/rom/lint/spec.rb +22 -0
- data/lib/rom/lint/test.rb +98 -0
- data/lib/rom/loader.rb +161 -0
- data/lib/rom/mapper/attribute_dsl.rb +480 -0
- data/lib/rom/mapper/dsl.rb +107 -0
- data/lib/rom/mapper/model_dsl.rb +61 -0
- data/lib/rom/mapper.rb +99 -0
- data/lib/rom/mapper_compiler.rb +84 -0
- data/lib/rom/memory/associations/many_to_many.rb +12 -0
- data/lib/rom/memory/associations/many_to_one.rb +12 -0
- data/lib/rom/memory/associations/one_to_many.rb +12 -0
- data/lib/rom/memory/associations/one_to_one.rb +12 -0
- data/lib/rom/memory/associations.rb +6 -0
- data/lib/rom/memory/commands.rb +60 -0
- data/lib/rom/memory/dataset.rb +127 -0
- data/lib/rom/memory/gateway.rb +66 -0
- data/lib/rom/memory/mapper_compiler.rb +10 -0
- data/lib/rom/memory/relation.rb +91 -0
- data/lib/rom/memory/schema.rb +32 -0
- data/lib/rom/memory/storage.rb +61 -0
- data/lib/rom/memory/types.rb +11 -0
- data/lib/rom/memory.rb +7 -0
- data/lib/rom/model_builder.rb +103 -0
- data/lib/rom/open_struct.rb +112 -0
- data/lib/rom/pipeline.rb +111 -0
- data/lib/rom/plugin.rb +130 -0
- data/lib/rom/plugins/class_methods.rb +37 -0
- data/lib/rom/plugins/command/schema.rb +45 -0
- data/lib/rom/plugins/command/timestamps.rb +149 -0
- data/lib/rom/plugins/dsl.rb +53 -0
- data/lib/rom/plugins/relation/changeset.rb +97 -0
- data/lib/rom/plugins/relation/instrumentation.rb +66 -0
- data/lib/rom/plugins/relation/registry_reader.rb +36 -0
- data/lib/rom/plugins/schema/timestamps.rb +59 -0
- data/lib/rom/plugins.rb +100 -0
- data/lib/rom/processor/composer.rb +37 -0
- data/lib/rom/processor/transformer.rb +415 -0
- data/lib/rom/processor.rb +30 -0
- data/lib/rom/registries/associations.rb +26 -0
- data/lib/rom/registries/commands.rb +11 -0
- data/lib/rom/registries/container.rb +12 -0
- data/lib/rom/registries/datasets.rb +21 -0
- data/lib/rom/registries/gateways.rb +8 -0
- data/lib/rom/registries/mappers.rb +21 -0
- data/lib/rom/registries/nestable.rb +32 -0
- data/lib/rom/registries/relations.rb +8 -0
- data/lib/rom/registries/root.rb +203 -0
- data/lib/rom/registries/schemas.rb +44 -0
- data/lib/rom/registries/views.rb +11 -0
- data/lib/rom/relation/class_interface.rb +61 -0
- data/lib/rom/relation/combined.rb +160 -0
- data/lib/rom/relation/commands.rb +65 -0
- data/lib/rom/relation/composite.rb +53 -0
- data/lib/rom/relation/curried.rb +129 -0
- data/lib/rom/relation/graph.rb +107 -0
- data/lib/rom/relation/loaded.rb +136 -0
- data/lib/rom/relation/materializable.rb +62 -0
- data/lib/rom/relation/name.rb +122 -0
- data/lib/rom/relation/wrap.rb +64 -0
- data/lib/rom/relation.rb +625 -0
- data/lib/rom/repository/class_interface.rb +162 -0
- data/lib/rom/repository/relation_reader.rb +48 -0
- data/lib/rom/repository/root.rb +75 -0
- data/lib/rom/repository/session.rb +60 -0
- data/lib/rom/repository.rb +179 -0
- data/lib/rom/schema/associations_dsl.rb +222 -0
- data/lib/rom/schema/inferrer.rb +106 -0
- data/lib/rom/schema.rb +471 -0
- data/lib/rom/settings.rb +141 -0
- data/lib/rom/setup.rb +297 -0
- data/lib/rom/struct.rb +99 -0
- data/lib/rom/struct_compiler.rb +114 -0
- data/lib/rom/support/configurable.rb +213 -0
- data/lib/rom/support/inflector.rb +31 -0
- data/lib/rom/support/memoizable.rb +61 -0
- data/lib/rom/support/notifications.rb +238 -0
- data/lib/rom/transaction.rb +26 -0
- data/lib/rom/transformer.rb +46 -0
- data/lib/rom/types.rb +74 -0
- data/lib/rom/version.rb +1 -1
- data/lib/rom-changeset.rb +4 -0
- data/lib/rom-core.rb +3 -0
- data/lib/rom-repository.rb +4 -0
- data/lib/rom.rb +3 -3
- metadata +302 -23
data/lib/rom/command.rb
ADDED
@@ -0,0 +1,351 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rom/support/configurable"
|
4
|
+
require "rom/types"
|
5
|
+
require "rom/initializer"
|
6
|
+
require "rom/pipeline"
|
7
|
+
require "rom/plugins/class_methods"
|
8
|
+
|
9
|
+
require "rom/commands/class_interface"
|
10
|
+
require "rom/commands/composite"
|
11
|
+
require "rom/commands/graph"
|
12
|
+
require "rom/commands/lazy"
|
13
|
+
|
14
|
+
require_relative "components/provider"
|
15
|
+
|
16
|
+
module ROM
|
17
|
+
# Abstract command class
|
18
|
+
#
|
19
|
+
# Provides a constructor accepting relation with options and basic behavior
|
20
|
+
# for calling, currying and composing commands.
|
21
|
+
#
|
22
|
+
# Typically command subclasses should inherit from specialized
|
23
|
+
# Create/Update/Delete, not this one.
|
24
|
+
#
|
25
|
+
# @abstract
|
26
|
+
#
|
27
|
+
# @api public
|
28
|
+
class Command
|
29
|
+
extend ROM::Provider(type: :command)
|
30
|
+
extend Plugins::ClassMethods
|
31
|
+
extend Initializer
|
32
|
+
extend ClassInterface
|
33
|
+
|
34
|
+
include Dry::Equalizer(:relation, :options)
|
35
|
+
include Commands
|
36
|
+
include Pipeline::Operator
|
37
|
+
|
38
|
+
setting :restrictable
|
39
|
+
setting :result
|
40
|
+
setting :input
|
41
|
+
setting :type
|
42
|
+
|
43
|
+
config.input = Hash
|
44
|
+
config.result = :many
|
45
|
+
|
46
|
+
# @!attribute [r] relation
|
47
|
+
# @return [Relation] Command's relation
|
48
|
+
param :relation
|
49
|
+
|
50
|
+
CommandType = Types::Strict::Symbol.enum(:create, :update, :delete)
|
51
|
+
Result = Types::Strict::Symbol.enum(:one, :many)
|
52
|
+
|
53
|
+
# @!attribute [r] type
|
54
|
+
# @return [Symbol] The command type, one of :create, :update or :delete
|
55
|
+
option :type, type: CommandType, optional: true
|
56
|
+
|
57
|
+
# @!attribute [r] config
|
58
|
+
# @return [Dry::Configurable::Config]
|
59
|
+
option :config, default: -> { self.class.config }
|
60
|
+
|
61
|
+
# @!attribute [r] source
|
62
|
+
# @return [Relation] The source relation
|
63
|
+
option :source, default: -> { relation }
|
64
|
+
|
65
|
+
# @!attribute [r] result
|
66
|
+
# @return [Symbol] Result type, either :one or :many
|
67
|
+
option :result, type: Result, default: -> { config.result }
|
68
|
+
|
69
|
+
# @!attribute [r] input
|
70
|
+
# @return [Proc, #call] Tuple processing function, typically uses Relation#input_schema
|
71
|
+
option :input, default: -> { config.input }
|
72
|
+
|
73
|
+
# @!attribute [r] curry_args
|
74
|
+
# @return [Array] Curried args
|
75
|
+
option :curry_args, default: -> { EMPTY_ARRAY }
|
76
|
+
|
77
|
+
# @!attribute [r] before
|
78
|
+
# @return [Array<Hash>] An array with before hooks configuration
|
79
|
+
option :before, Types::Coercible::Array, reader: false, default: -> { self.class.before }
|
80
|
+
|
81
|
+
# @!attribute [r] after
|
82
|
+
# @return [Array<Hash>] An array with after hooks configuration
|
83
|
+
option :after, Types::Coercible::Array, reader: false, default: -> { self.class.after }
|
84
|
+
|
85
|
+
# Return name of this command's relation
|
86
|
+
#
|
87
|
+
# @return [ROM::Relation::Name]
|
88
|
+
#
|
89
|
+
# @api public
|
90
|
+
def name
|
91
|
+
relation.name
|
92
|
+
end
|
93
|
+
|
94
|
+
# Return gateway of this command's relation
|
95
|
+
#
|
96
|
+
# @return [Symbol]
|
97
|
+
#
|
98
|
+
# @api public
|
99
|
+
def gateway
|
100
|
+
relation.gateway
|
101
|
+
end
|
102
|
+
|
103
|
+
# Execute the command
|
104
|
+
#
|
105
|
+
# @abstract
|
106
|
+
#
|
107
|
+
# @return [Array] an array with inserted tuples
|
108
|
+
#
|
109
|
+
# @api private
|
110
|
+
def execute(*)
|
111
|
+
raise(
|
112
|
+
NotImplementedError,
|
113
|
+
"#{self.class}##{__method__} must be implemented"
|
114
|
+
)
|
115
|
+
end
|
116
|
+
|
117
|
+
# Call the command and return one or many tuples
|
118
|
+
#
|
119
|
+
# This method will apply before/after hooks automatically
|
120
|
+
#
|
121
|
+
# @api public
|
122
|
+
def call(*args, &block)
|
123
|
+
tuples =
|
124
|
+
if hooks?
|
125
|
+
prepared =
|
126
|
+
if curried?
|
127
|
+
apply_hooks(before_hooks, *(curry_args + args))
|
128
|
+
else
|
129
|
+
apply_hooks(before_hooks, *args)
|
130
|
+
end
|
131
|
+
|
132
|
+
result = prepared ? execute(prepared, &block) : execute(&block)
|
133
|
+
|
134
|
+
if curried?
|
135
|
+
if !args.empty?
|
136
|
+
apply_hooks(after_hooks, result, *args)
|
137
|
+
elsif curry_args.size > 1
|
138
|
+
apply_hooks(after_hooks, result, curry_args[1])
|
139
|
+
else
|
140
|
+
apply_hooks(after_hooks, result)
|
141
|
+
end
|
142
|
+
else
|
143
|
+
apply_hooks(after_hooks, result, *args[1..args.size - 1])
|
144
|
+
end
|
145
|
+
else
|
146
|
+
execute(*(curry_args + args), &block)
|
147
|
+
end
|
148
|
+
|
149
|
+
if one?
|
150
|
+
tuples.first
|
151
|
+
else
|
152
|
+
tuples
|
153
|
+
end
|
154
|
+
end
|
155
|
+
alias_method :[], :call
|
156
|
+
|
157
|
+
# Curry this command with provided args
|
158
|
+
#
|
159
|
+
# Curried command can be called without args. If argument is a graph input processor,
|
160
|
+
# lazy command will be returned, which is used for handling nested input hashes.
|
161
|
+
#
|
162
|
+
# @return [Command, Lazy]
|
163
|
+
#
|
164
|
+
# @api public
|
165
|
+
def curry(*args)
|
166
|
+
if curry_args.empty? && args.first.is_a?(Graph::InputEvaluator)
|
167
|
+
Lazy[self].new(self, *args)
|
168
|
+
else
|
169
|
+
self.class.build(relation, **options, curry_args: args)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
# Compose this command with other commands
|
174
|
+
#
|
175
|
+
# Composed commands can handle nested input
|
176
|
+
#
|
177
|
+
# @return [Command::Graph]
|
178
|
+
#
|
179
|
+
# @api public
|
180
|
+
def combine(*others)
|
181
|
+
Graph.new(self, others)
|
182
|
+
end
|
183
|
+
|
184
|
+
# Check if this command is curried
|
185
|
+
#
|
186
|
+
# @return [TrueClass, FalseClass]
|
187
|
+
#
|
188
|
+
# @api public
|
189
|
+
def curried?
|
190
|
+
!curry_args.empty?
|
191
|
+
end
|
192
|
+
|
193
|
+
# Return a new command with appended before hooks
|
194
|
+
#
|
195
|
+
# @param [Array<Hash>] hooks A list of before hooks configurations
|
196
|
+
#
|
197
|
+
# @return [Command]
|
198
|
+
#
|
199
|
+
# @api public
|
200
|
+
def before(*hooks)
|
201
|
+
self.class.new(relation, **options, before: before_hooks + hooks)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Return a new command with appended after hooks
|
205
|
+
#
|
206
|
+
# @param [Array<Hash>] hooks A list of after hooks configurations
|
207
|
+
#
|
208
|
+
# @return [Command]
|
209
|
+
#
|
210
|
+
# @api public
|
211
|
+
def after(*hooks)
|
212
|
+
self.class.new(relation, **options, after: after_hooks + hooks)
|
213
|
+
end
|
214
|
+
|
215
|
+
# List of before hooks
|
216
|
+
#
|
217
|
+
# @return [Array]
|
218
|
+
#
|
219
|
+
# @api public
|
220
|
+
def before_hooks
|
221
|
+
options[:before]
|
222
|
+
end
|
223
|
+
|
224
|
+
# List of after hooks
|
225
|
+
#
|
226
|
+
# @return [Array]
|
227
|
+
#
|
228
|
+
# @api public
|
229
|
+
def after_hooks
|
230
|
+
options[:after]
|
231
|
+
end
|
232
|
+
|
233
|
+
# Return a new command with other source relation
|
234
|
+
#
|
235
|
+
# This can be used to restrict command with a specific relation
|
236
|
+
#
|
237
|
+
# @return [Command]
|
238
|
+
#
|
239
|
+
# @api public
|
240
|
+
def new(new_relation)
|
241
|
+
self.class.build(new_relation, **options, source: relation)
|
242
|
+
end
|
243
|
+
|
244
|
+
# Check if this command has any hooks
|
245
|
+
#
|
246
|
+
# @api private
|
247
|
+
def hooks?
|
248
|
+
!before_hooks.empty? || !after_hooks.empty?
|
249
|
+
end
|
250
|
+
|
251
|
+
# Check if this command is lazy
|
252
|
+
#
|
253
|
+
# @return [false]
|
254
|
+
#
|
255
|
+
# @api private
|
256
|
+
def lazy?
|
257
|
+
false
|
258
|
+
end
|
259
|
+
|
260
|
+
# Check if this command is a graph
|
261
|
+
#
|
262
|
+
# @return [false]
|
263
|
+
#
|
264
|
+
# @api private
|
265
|
+
def graph?
|
266
|
+
false
|
267
|
+
end
|
268
|
+
|
269
|
+
# Check if this command returns a single tuple
|
270
|
+
#
|
271
|
+
# @return [TrueClass,FalseClass]
|
272
|
+
#
|
273
|
+
# @api private
|
274
|
+
def one?
|
275
|
+
result.equal?(:one)
|
276
|
+
end
|
277
|
+
|
278
|
+
# Check if this command returns many tuples
|
279
|
+
#
|
280
|
+
# @return [TrueClass,FalseClass]
|
281
|
+
#
|
282
|
+
# @api private
|
283
|
+
def many?
|
284
|
+
result.equal?(:many)
|
285
|
+
end
|
286
|
+
|
287
|
+
# Check if this command is restrictible through relation
|
288
|
+
#
|
289
|
+
# @return [TrueClass,FalseClass]
|
290
|
+
#
|
291
|
+
# @api private
|
292
|
+
def restrictible?
|
293
|
+
config.restrictable.equal?(true)
|
294
|
+
end
|
295
|
+
|
296
|
+
# Yields tuples for insertion or return an enumerator
|
297
|
+
#
|
298
|
+
# @api private
|
299
|
+
def map_input_tuples(tuples, &mapper)
|
300
|
+
return enum_for(:with_input_tuples, tuples) unless mapper
|
301
|
+
|
302
|
+
if tuples.respond_to? :merge
|
303
|
+
mapper[tuples]
|
304
|
+
else
|
305
|
+
tuples.map(&mapper)
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
private
|
310
|
+
|
311
|
+
# Hook called by Pipeline to get composite class for commands
|
312
|
+
#
|
313
|
+
# @return [Class]
|
314
|
+
#
|
315
|
+
# @api private
|
316
|
+
def composite_class
|
317
|
+
Command::Composite
|
318
|
+
end
|
319
|
+
|
320
|
+
# Apply provided hooks
|
321
|
+
#
|
322
|
+
# Used by #call
|
323
|
+
#
|
324
|
+
# @return [Array<Hash>]
|
325
|
+
#
|
326
|
+
# @api private
|
327
|
+
def apply_hooks(hooks, tuples, *args)
|
328
|
+
hooks.reduce(tuples) do |a, e|
|
329
|
+
if e.is_a?(Hash)
|
330
|
+
hook_meth, hook_args = e.to_a.flatten(1)
|
331
|
+
__send__(hook_meth, a, *args, **hook_args)
|
332
|
+
else
|
333
|
+
__send__(e, a, *args)
|
334
|
+
end
|
335
|
+
end
|
336
|
+
end
|
337
|
+
|
338
|
+
# Pipes a dataset through command's relation
|
339
|
+
#
|
340
|
+
# @return [Array]
|
341
|
+
#
|
342
|
+
# @api private
|
343
|
+
def wrap_dataset(dataset)
|
344
|
+
if relation.is_a?(Relation::Composite)
|
345
|
+
relation.new(dataset).to_a
|
346
|
+
else
|
347
|
+
dataset
|
348
|
+
end
|
349
|
+
end
|
350
|
+
end
|
351
|
+
end
|
@@ -0,0 +1,201 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rom/initializer"
|
4
|
+
require "rom/commands/graph"
|
5
|
+
require "rom/command_proxy"
|
6
|
+
require "rom/registries/root"
|
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 Initializer
|
18
|
+
|
19
|
+
# @!attribute [r] id
|
20
|
+
# @return [Symbol] The command registry identifier
|
21
|
+
option :id, optional: true
|
22
|
+
|
23
|
+
# @!attribute [r] command_class
|
24
|
+
# @return [Symbol] The command command_class
|
25
|
+
option :command_class, optional: true
|
26
|
+
|
27
|
+
# @!attribute [r] registry
|
28
|
+
# @return [Registry::Root]
|
29
|
+
option :registry, default: -> { Registry::Root.new }
|
30
|
+
|
31
|
+
# @!attribute [r] plugins
|
32
|
+
# @return [Array<Symbol>] a list of optional plugins that will be enabled for commands
|
33
|
+
option :plugins, optional: true, default: -> { EMPTY_HASH }
|
34
|
+
|
35
|
+
# @!attribute [r] meta
|
36
|
+
# @return [Array<Symbol>] Meta data for a command
|
37
|
+
option :meta, optional: true
|
38
|
+
|
39
|
+
# @!attribute [r] cache
|
40
|
+
# @return [Cache] local cache instance
|
41
|
+
option :cache, default: -> { Cache.new }
|
42
|
+
|
43
|
+
# Return a specific command command_class for a given adapter and relation AST
|
44
|
+
#
|
45
|
+
# This class holds its own registry where all generated commands are being
|
46
|
+
# stored
|
47
|
+
#
|
48
|
+
# CommandProxy is returned for complex command graphs as they expect root
|
49
|
+
# relation name to be present in the input, which we don't want to have
|
50
|
+
# in repositories. It might be worth looking into removing this requirement
|
51
|
+
# from rom core Command::Graph API.
|
52
|
+
#
|
53
|
+
# @overload [](id, adapter, ast, plugins, meta)
|
54
|
+
# @param id [Symbol] The command identifier
|
55
|
+
# @param adapter [Symbol] The adapter identifier
|
56
|
+
# @param ast [Array] The AST representation of a relation
|
57
|
+
# @param plugins [Array<Symbol>] A list of optional command plugins that should be used
|
58
|
+
# @param meta [Hash] Meta data for a command
|
59
|
+
#
|
60
|
+
# @return [Command, CommandProxy]
|
61
|
+
#
|
62
|
+
# @api private
|
63
|
+
def call(*args)
|
64
|
+
cache.fetch_or_store(args.hash) do
|
65
|
+
id, adapter, ast, plugins, plugins_options, meta = args
|
66
|
+
|
67
|
+
component = registry.components.get(:commands, id: id)
|
68
|
+
|
69
|
+
command_class =
|
70
|
+
if component
|
71
|
+
component.constant
|
72
|
+
else
|
73
|
+
Command.adapter_namespace(adapter).const_get(Inflector.classify(id))
|
74
|
+
end
|
75
|
+
|
76
|
+
plugins_with_opts = Array(plugins)
|
77
|
+
.map { |plugin| [plugin, plugins_options.fetch(plugin) { EMPTY_HASH }] }
|
78
|
+
.to_h
|
79
|
+
|
80
|
+
compiler = with(
|
81
|
+
id: id,
|
82
|
+
command_class: command_class,
|
83
|
+
adapter: adapter,
|
84
|
+
plugins: plugins_with_opts,
|
85
|
+
meta: meta
|
86
|
+
)
|
87
|
+
|
88
|
+
graph_opts = compiler.visit(ast)
|
89
|
+
command = ROM::Commands::Graph.build(registry.root.commands, graph_opts)
|
90
|
+
|
91
|
+
if command.graph?
|
92
|
+
root = Inflector.singularize(command.name.relation).to_sym
|
93
|
+
CommandProxy.new(command, root)
|
94
|
+
elsif command.lazy?
|
95
|
+
command.unwrap
|
96
|
+
else
|
97
|
+
command
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
alias_method :[], :call
|
102
|
+
|
103
|
+
# @api private
|
104
|
+
def visit(ast, *args)
|
105
|
+
name, node = ast
|
106
|
+
__send__(:"visit_#{name}", node, *args)
|
107
|
+
end
|
108
|
+
|
109
|
+
# @api private
|
110
|
+
def relations
|
111
|
+
registry.root.relations
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
# @api private
|
117
|
+
def visit_relation(node, parent_relation = nil)
|
118
|
+
name, header, rel_meta = node
|
119
|
+
other = header.map { |attr| visit(attr, name) }.compact
|
120
|
+
|
121
|
+
key = register_command(name, rel_meta, parent_relation)
|
122
|
+
|
123
|
+
default_mapping =
|
124
|
+
if rel_meta[:combine_command_class] == :many
|
125
|
+
name
|
126
|
+
else
|
127
|
+
{Inflector.singularize(name).to_sym => name}
|
128
|
+
end
|
129
|
+
|
130
|
+
mapping =
|
131
|
+
if parent_relation
|
132
|
+
associations = relations[parent_relation].associations
|
133
|
+
|
134
|
+
assoc = associations[rel_meta[:combine_name]]
|
135
|
+
|
136
|
+
if assoc
|
137
|
+
{assoc.key => assoc.target.name.to_sym}
|
138
|
+
else
|
139
|
+
default_mapping
|
140
|
+
end
|
141
|
+
else
|
142
|
+
default_mapping
|
143
|
+
end
|
144
|
+
|
145
|
+
if other.empty?
|
146
|
+
[mapping, key]
|
147
|
+
else
|
148
|
+
[mapping, [key, other]]
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# @api private
|
153
|
+
def visit_attribute(*)
|
154
|
+
nil
|
155
|
+
end
|
156
|
+
|
157
|
+
# Build a command object for a specific relation
|
158
|
+
#
|
159
|
+
# The command will be prepared for handling associations if it's a combined
|
160
|
+
# relation. Additional plugins will be enabled if they are configured for
|
161
|
+
# this compiler.
|
162
|
+
#
|
163
|
+
# @param [Symbol] rel_name A relation identifier from the container registry
|
164
|
+
# @param [Hash] rel_meta Meta information from relation AST
|
165
|
+
# @param [Symbol] parent_relation Optional parent relation identifier
|
166
|
+
#
|
167
|
+
# @return [ROM::Command]
|
168
|
+
#
|
169
|
+
# @api private
|
170
|
+
def register_command(rel_name, rel_meta, parent_relation = nil)
|
171
|
+
options = {
|
172
|
+
rel_name: rel_name,
|
173
|
+
meta: meta,
|
174
|
+
rel_meta: rel_meta,
|
175
|
+
parent_relation: parent_relation,
|
176
|
+
plugins: plugins
|
177
|
+
}
|
178
|
+
|
179
|
+
key = "commands.#{rel_name}.#{id}-compiled-#{options.hash}"
|
180
|
+
|
181
|
+
registry.fetch(key) do
|
182
|
+
relation = relations[rel_name]
|
183
|
+
klass = command_class.create_class(relation: relation, **options)
|
184
|
+
|
185
|
+
if gateways.key?(relation.gateway)
|
186
|
+
# TODO: add explicit specs covering this case
|
187
|
+
gateways[relation.gateway].command(klass, relation: relation, **options)
|
188
|
+
else
|
189
|
+
klass.build(relation, **options)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
key
|
194
|
+
end
|
195
|
+
|
196
|
+
# @api private
|
197
|
+
def gateways
|
198
|
+
registry.root.gateways
|
199
|
+
end
|
200
|
+
end
|
201
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rom/support/inflector"
|
4
|
+
|
5
|
+
module ROM
|
6
|
+
# TODO: look into making command graphs work without the root key in the input
|
7
|
+
# so that we can get rid of this wrapper
|
8
|
+
#
|
9
|
+
# @api private
|
10
|
+
class CommandProxy
|
11
|
+
attr_reader :command
|
12
|
+
|
13
|
+
attr_reader :root
|
14
|
+
|
15
|
+
# @api private
|
16
|
+
def initialize(command, root)
|
17
|
+
@command = command
|
18
|
+
@root = root
|
19
|
+
end
|
20
|
+
|
21
|
+
# @api private
|
22
|
+
def call(input)
|
23
|
+
command.call(root => input)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @api private
|
27
|
+
def >>(other)
|
28
|
+
self.class.new(command >> other, root)
|
29
|
+
end
|
30
|
+
|
31
|
+
# @api private
|
32
|
+
def restrictible?
|
33
|
+
command.restrictible?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|