rom 5.3.2 → 6.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -47
  3. data/LICENSE +1 -1
  4. data/README.md +6 -6
  5. data/lib/rom/array_dataset.rb +46 -0
  6. data/lib/rom/associations/abstract.rb +217 -0
  7. data/lib/rom/associations/definitions/abstract.rb +150 -0
  8. data/lib/rom/associations/definitions/many_to_many.rb +29 -0
  9. data/lib/rom/associations/definitions/many_to_one.rb +14 -0
  10. data/lib/rom/associations/definitions/one_to_many.rb +14 -0
  11. data/lib/rom/associations/definitions/one_to_one.rb +14 -0
  12. data/lib/rom/associations/definitions/one_to_one_through.rb +14 -0
  13. data/lib/rom/associations/definitions.rb +7 -0
  14. data/lib/rom/associations/many_to_many.rb +128 -0
  15. data/lib/rom/associations/many_to_one.rb +65 -0
  16. data/lib/rom/associations/one_to_many.rb +65 -0
  17. data/lib/rom/associations/one_to_one.rb +13 -0
  18. data/lib/rom/associations/one_to_one_through.rb +13 -0
  19. data/lib/rom/associations/through_identifier.rb +41 -0
  20. data/lib/rom/attribute.rb +425 -0
  21. data/lib/rom/auto_curry.rb +70 -0
  22. data/lib/rom/cache.rb +87 -0
  23. data/lib/rom/changeset/associated.rb +110 -0
  24. data/lib/rom/changeset/create.rb +18 -0
  25. data/lib/rom/changeset/delete.rb +15 -0
  26. data/lib/rom/changeset/extensions/relation.rb +26 -0
  27. data/lib/rom/changeset/pipe.rb +81 -0
  28. data/lib/rom/changeset/pipe_registry.rb +27 -0
  29. data/lib/rom/changeset/stateful.rb +285 -0
  30. data/lib/rom/changeset/update.rb +81 -0
  31. data/lib/rom/changeset.rb +185 -0
  32. data/lib/rom/command.rb +351 -0
  33. data/lib/rom/command_compiler.rb +201 -0
  34. data/lib/rom/command_proxy.rb +36 -0
  35. data/lib/rom/commands/class_interface.rb +236 -0
  36. data/lib/rom/commands/composite.rb +55 -0
  37. data/lib/rom/commands/create.rb +15 -0
  38. data/lib/rom/commands/delete.rb +16 -0
  39. data/lib/rom/commands/graph/class_interface.rb +64 -0
  40. data/lib/rom/commands/graph/input_evaluator.rb +94 -0
  41. data/lib/rom/commands/graph.rb +88 -0
  42. data/lib/rom/commands/lazy/create.rb +35 -0
  43. data/lib/rom/commands/lazy/delete.rb +39 -0
  44. data/lib/rom/commands/lazy/update.rb +46 -0
  45. data/lib/rom/commands/lazy.rb +106 -0
  46. data/lib/rom/commands/update.rb +16 -0
  47. data/lib/rom/commands.rb +5 -0
  48. data/lib/rom/compat/auto_registration.rb +115 -0
  49. data/lib/rom/compat/auto_registration_strategies/base.rb +29 -0
  50. data/lib/rom/compat/auto_registration_strategies/custom_namespace.rb +84 -0
  51. data/lib/rom/compat/auto_registration_strategies/no_namespace.rb +33 -0
  52. data/lib/rom/compat/auto_registration_strategies/with_namespace.rb +29 -0
  53. data/lib/rom/compat/command.rb +74 -0
  54. data/lib/rom/compat/components/dsl/schema.rb +130 -0
  55. data/lib/rom/compat/components.rb +91 -0
  56. data/lib/rom/compat/global.rb +17 -0
  57. data/lib/rom/compat/mapper.rb +22 -0
  58. data/lib/rom/compat/registries.rb +47 -0
  59. data/lib/rom/compat/relation.rb +40 -0
  60. data/lib/rom/compat/schema/dsl.rb +260 -0
  61. data/lib/rom/compat/setting_proxy.rb +44 -0
  62. data/lib/rom/compat/setup.rb +151 -0
  63. data/lib/rom/compat/transformer.rb +49 -0
  64. data/lib/rom/compat.rb +22 -0
  65. data/lib/rom/components/association.rb +26 -0
  66. data/lib/rom/components/command.rb +24 -0
  67. data/lib/rom/components/core.rb +148 -0
  68. data/lib/rom/components/dataset.rb +60 -0
  69. data/lib/rom/components/dsl/association.rb +47 -0
  70. data/lib/rom/components/dsl/command.rb +60 -0
  71. data/lib/rom/components/dsl/core.rb +126 -0
  72. data/lib/rom/components/dsl/dataset.rb +33 -0
  73. data/lib/rom/components/dsl/gateway.rb +14 -0
  74. data/lib/rom/components/dsl/mapper.rb +70 -0
  75. data/lib/rom/components/dsl/relation.rb +49 -0
  76. data/lib/rom/components/dsl/schema.rb +150 -0
  77. data/lib/rom/components/dsl/view.rb +82 -0
  78. data/lib/rom/components/dsl.rb +255 -0
  79. data/lib/rom/components/gateway.rb +50 -0
  80. data/lib/rom/components/mapper.rb +29 -0
  81. data/lib/rom/components/provider.rb +160 -0
  82. data/lib/rom/components/registry.rb +154 -0
  83. data/lib/rom/components/relation.rb +41 -0
  84. data/lib/rom/components/schema.rb +61 -0
  85. data/lib/rom/components/view.rb +55 -0
  86. data/lib/rom/components.rb +55 -0
  87. data/lib/rom/configuration_dsl.rb +4 -0
  88. data/lib/rom/constants.rb +135 -0
  89. data/lib/rom/container.rb +182 -0
  90. data/lib/rom/core.rb +125 -0
  91. data/lib/rom/data_proxy.rb +97 -0
  92. data/lib/rom/enumerable_dataset.rb +70 -0
  93. data/lib/rom/gateway.rb +232 -0
  94. data/lib/rom/global.rb +56 -0
  95. data/lib/rom/header/attribute.rb +190 -0
  96. data/lib/rom/header.rb +198 -0
  97. data/lib/rom/inferrer.rb +55 -0
  98. data/lib/rom/initializer.rb +80 -0
  99. data/lib/rom/lint/enumerable_dataset.rb +56 -0
  100. data/lib/rom/lint/gateway.rb +120 -0
  101. data/lib/rom/lint/linter.rb +79 -0
  102. data/lib/rom/lint/spec.rb +22 -0
  103. data/lib/rom/lint/test.rb +98 -0
  104. data/lib/rom/loader.rb +161 -0
  105. data/lib/rom/mapper/attribute_dsl.rb +480 -0
  106. data/lib/rom/mapper/dsl.rb +107 -0
  107. data/lib/rom/mapper/model_dsl.rb +61 -0
  108. data/lib/rom/mapper.rb +99 -0
  109. data/lib/rom/mapper_compiler.rb +84 -0
  110. data/lib/rom/memory/associations/many_to_many.rb +12 -0
  111. data/lib/rom/memory/associations/many_to_one.rb +12 -0
  112. data/lib/rom/memory/associations/one_to_many.rb +12 -0
  113. data/lib/rom/memory/associations/one_to_one.rb +12 -0
  114. data/lib/rom/memory/associations.rb +6 -0
  115. data/lib/rom/memory/commands.rb +60 -0
  116. data/lib/rom/memory/dataset.rb +127 -0
  117. data/lib/rom/memory/gateway.rb +66 -0
  118. data/lib/rom/memory/mapper_compiler.rb +10 -0
  119. data/lib/rom/memory/relation.rb +91 -0
  120. data/lib/rom/memory/schema.rb +32 -0
  121. data/lib/rom/memory/storage.rb +61 -0
  122. data/lib/rom/memory/types.rb +11 -0
  123. data/lib/rom/memory.rb +7 -0
  124. data/lib/rom/model_builder.rb +103 -0
  125. data/lib/rom/open_struct.rb +112 -0
  126. data/lib/rom/pipeline.rb +111 -0
  127. data/lib/rom/plugin.rb +130 -0
  128. data/lib/rom/plugins/class_methods.rb +37 -0
  129. data/lib/rom/plugins/command/schema.rb +45 -0
  130. data/lib/rom/plugins/command/timestamps.rb +149 -0
  131. data/lib/rom/plugins/dsl.rb +53 -0
  132. data/lib/rom/plugins/relation/changeset.rb +97 -0
  133. data/lib/rom/plugins/relation/instrumentation.rb +66 -0
  134. data/lib/rom/plugins/relation/registry_reader.rb +36 -0
  135. data/lib/rom/plugins/schema/timestamps.rb +59 -0
  136. data/lib/rom/plugins.rb +100 -0
  137. data/lib/rom/processor/composer.rb +37 -0
  138. data/lib/rom/processor/transformer.rb +415 -0
  139. data/lib/rom/processor.rb +30 -0
  140. data/lib/rom/registries/associations.rb +26 -0
  141. data/lib/rom/registries/commands.rb +11 -0
  142. data/lib/rom/registries/container.rb +12 -0
  143. data/lib/rom/registries/datasets.rb +21 -0
  144. data/lib/rom/registries/gateways.rb +8 -0
  145. data/lib/rom/registries/mappers.rb +21 -0
  146. data/lib/rom/registries/nestable.rb +32 -0
  147. data/lib/rom/registries/relations.rb +8 -0
  148. data/lib/rom/registries/root.rb +203 -0
  149. data/lib/rom/registries/schemas.rb +44 -0
  150. data/lib/rom/registries/views.rb +11 -0
  151. data/lib/rom/relation/class_interface.rb +61 -0
  152. data/lib/rom/relation/combined.rb +160 -0
  153. data/lib/rom/relation/commands.rb +65 -0
  154. data/lib/rom/relation/composite.rb +53 -0
  155. data/lib/rom/relation/curried.rb +129 -0
  156. data/lib/rom/relation/graph.rb +107 -0
  157. data/lib/rom/relation/loaded.rb +136 -0
  158. data/lib/rom/relation/materializable.rb +62 -0
  159. data/lib/rom/relation/name.rb +122 -0
  160. data/lib/rom/relation/wrap.rb +64 -0
  161. data/lib/rom/relation.rb +625 -0
  162. data/lib/rom/repository/class_interface.rb +162 -0
  163. data/lib/rom/repository/relation_reader.rb +48 -0
  164. data/lib/rom/repository/root.rb +75 -0
  165. data/lib/rom/repository/session.rb +60 -0
  166. data/lib/rom/repository.rb +179 -0
  167. data/lib/rom/schema/associations_dsl.rb +222 -0
  168. data/lib/rom/schema/inferrer.rb +106 -0
  169. data/lib/rom/schema.rb +471 -0
  170. data/lib/rom/settings.rb +141 -0
  171. data/lib/rom/setup.rb +297 -0
  172. data/lib/rom/struct.rb +99 -0
  173. data/lib/rom/struct_compiler.rb +114 -0
  174. data/lib/rom/support/configurable.rb +213 -0
  175. data/lib/rom/support/inflector.rb +31 -0
  176. data/lib/rom/support/memoizable.rb +61 -0
  177. data/lib/rom/support/notifications.rb +238 -0
  178. data/lib/rom/transaction.rb +26 -0
  179. data/lib/rom/transformer.rb +46 -0
  180. data/lib/rom/types.rb +74 -0
  181. data/lib/rom/version.rb +1 -1
  182. data/lib/rom-changeset.rb +4 -0
  183. data/lib/rom-core.rb +3 -0
  184. data/lib/rom-repository.rb +4 -0
  185. data/lib/rom.rb +3 -3
  186. metadata +273 -36
@@ -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