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.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -65
  3. data/LICENSE +1 -1
  4. data/README.md +7 -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 +302 -23
@@ -0,0 +1,236 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/class_builder"
4
+
5
+ module ROM
6
+ # Base command class with factory class-level interface and setup-related logic
7
+ #
8
+ # @private
9
+ class Command
10
+ module ClassInterface
11
+ # This hook sets up default class state
12
+ #
13
+ # @api private
14
+ def inherited(klass)
15
+ super
16
+ klass.instance_variable_set(:@before, before.dup)
17
+ klass.instance_variable_set(:@after, after.dup)
18
+ end
19
+
20
+ # Sets up the base class
21
+ #
22
+ # @api private
23
+ def self.extended(klass)
24
+ super
25
+ klass.set_hooks(:before, [])
26
+ klass.set_hooks(:after, [])
27
+ end
28
+
29
+ # Return adapter specific sub-class based on the adapter identifier
30
+ #
31
+ # This is a syntax sugar to make things consistent
32
+ #
33
+ # @example
34
+ # ROM::Commands::Create[:memory]
35
+ # # => ROM::Memory::Commands::Create
36
+ #
37
+ # @param [Symbol] adapter identifier
38
+ #
39
+ # @return [Class]
40
+ #
41
+ # @api public
42
+ def [](adapter)
43
+ adapter_namespace(adapter).const_get(Inflector.demodulize(name))
44
+ end
45
+
46
+ # Return namespaces that contains command subclasses of a specific adapter
47
+ #
48
+ # @param [Symbol] adapter identifier
49
+ #
50
+ # @return [Module]
51
+ #
52
+ # @api private
53
+ def adapter_namespace(adapter)
54
+ ROM.adapters.fetch(adapter).const_get(:Commands)
55
+ rescue KeyError
56
+ raise AdapterNotPresentError.new(adapter, :relation)
57
+ end
58
+
59
+ # Build a command class for a specific relation with options
60
+ #
61
+ # @example
62
+ # class CreateUser < ROM::Commands::Create[:memory]
63
+ # end
64
+ #
65
+ # command = CreateUser.build(rom.relations[:users])
66
+ #
67
+ # @param [Relation] relation
68
+ # @param [Hash] options
69
+ #
70
+ # @return [Command]
71
+ #
72
+ # @api public
73
+ def build(relation, **options)
74
+ new(relation, **options)
75
+ end
76
+
77
+ # Create a command class with a specific type
78
+ #
79
+ # @param [Symbol] name Command name
80
+ # @param [Class] type Command class
81
+ #
82
+ # @yield [Class]
83
+ #
84
+ # @return [Class, Object]
85
+ #
86
+ # @api public
87
+ def create_class(type: self, meta: {}, rel_meta: {}, plugins: {}, **, &block)
88
+ klass = Dry::Core::ClassBuilder.new(name: type.name, parent: type).call
89
+
90
+ result = meta.fetch(:result, :one)
91
+ klass.config.result = rel_meta.fetch(:combine_type, result)
92
+
93
+ meta.each do |name, value|
94
+ if klass.respond_to?(name)
95
+ klass.public_send(name, value)
96
+ else
97
+ klass.config[name] = value
98
+ end
99
+ end
100
+
101
+ plugins.each do |plugin, options|
102
+ klass.use(plugin, **options)
103
+ end
104
+
105
+ if block
106
+ yield(klass)
107
+ else
108
+ klass
109
+ end
110
+ end
111
+
112
+ # Return configured adapter identifier
113
+ #
114
+ # @return [Symbol]
115
+ #
116
+ # @api public
117
+ def adapter
118
+ config.component.adapter
119
+ end
120
+
121
+ # Set before-execute hooks
122
+ #
123
+ # @overload before(hook)
124
+ # Set an before hook as a method name
125
+ #
126
+ # @example
127
+ # class CreateUser < ROM::Commands::Create[:sql]
128
+ # relation :users
129
+ # register_as :create
130
+ #
131
+ # before :my_hook
132
+ #
133
+ # def my_hook(tuple, *)
134
+ # puts "hook called#
135
+ # end
136
+ # end
137
+ #
138
+ # @overload before(hook_opts)
139
+ # Set an before hook as a method name with arguments
140
+ #
141
+ # @example
142
+ # class CreateUser < ROM::Commands::Create[:sql]
143
+ # relation :users
144
+ # register_as :create
145
+ #
146
+ # before my_hook: { arg1: 1, arg2: 2 }
147
+ #
148
+ # def my_hook(tuple, arg1:, arg2:)
149
+ # puts "hook called with args: #{arg1} and #{arg2}"
150
+ # end
151
+ # end
152
+ #
153
+ # @param [Hash<Symbol=>Hash>] hook Options with method name and pre-set args
154
+ #
155
+ # @return [Array<Hash, Symbol>] A list of all configured before hooks
156
+ #
157
+ # @api public
158
+ def before(*hooks)
159
+ if hooks.empty?
160
+ @before
161
+ else
162
+ set_hooks(:before, hooks)
163
+ end
164
+ end
165
+
166
+ # Set after-execute hooks
167
+ #
168
+ # @overload after(hook)
169
+ # Set an after hook as a method name
170
+ #
171
+ # @example
172
+ # class CreateUser < ROM::Commands::Create[:sql]
173
+ # relation :users
174
+ # register_as :create
175
+ #
176
+ # after :my_hook
177
+ #
178
+ # def my_hook(tuple, *)
179
+ # puts "hook called#
180
+ # end
181
+ # end
182
+ #
183
+ # @overload after(hook_opts)
184
+ # Set an after hook as a method name with arguments
185
+ #
186
+ # @example
187
+ # class CreateUser < ROM::Commands::Create[:sql]
188
+ # relation :users
189
+ # register_as :create
190
+ #
191
+ # after my_hook: { arg1: 1, arg1: 2 }
192
+ #
193
+ # def my_hook(tuple, arg1:, arg2:)
194
+ # puts "hook called with args: #{arg1} and #{arg2}"
195
+ # end
196
+ # end
197
+ #
198
+ # @param [Hash<Symbol=>Hash>] hook Options with method name and pre-set args
199
+ #
200
+ # @return [Array<Hash, Symbol>] A list of all configured after hooks
201
+ #
202
+ # @api public
203
+ def after(*hooks)
204
+ if hooks.empty?
205
+ @after
206
+ else
207
+ set_hooks(:after, hooks)
208
+ end
209
+ end
210
+
211
+ # Set new or more hooks
212
+ #
213
+ # @api private
214
+ def set_hooks(type, hooks)
215
+ ivar = :"@#{type}"
216
+
217
+ if instance_variable_defined?(ivar)
218
+ instance_variable_get(ivar).concat(hooks)
219
+ else
220
+ instance_variable_set(ivar, hooks)
221
+ end
222
+ end
223
+
224
+ # Return default name of the command class based on its name
225
+ #
226
+ # During setup phase this is used by defalut as `register_as` option
227
+ #
228
+ # @return [Symbol]
229
+ #
230
+ # @api private
231
+ def default_name
232
+ Inflector.underscore(Inflector.demodulize(name)).to_sym
233
+ end
234
+ end
235
+ end
236
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rom/pipeline"
4
+
5
+ module ROM
6
+ module Commands
7
+ # Composite command that consists of left and right commands
8
+ #
9
+ # @api public
10
+ class Composite < Pipeline::Composite
11
+ # Calls the composite command
12
+ #
13
+ # Right command is called with a result from the left one
14
+ #
15
+ # @return [Object]
16
+ #
17
+ # @api public
18
+ def call(*args)
19
+ response = left.call(*args)
20
+
21
+ if response.nil? || (many? && response.empty?)
22
+ return one? ? nil : EMPTY_ARRAY
23
+ end
24
+
25
+ if one? && !graph?
26
+ if right.is_a?(Command) || right.is_a?(Commands::Composite)
27
+ right.call([response].first)
28
+ else
29
+ right.call([response]).first
30
+ end
31
+ elsif one? && graph?
32
+ right.call(response).first
33
+ else
34
+ right.call(response)
35
+ end
36
+ end
37
+ alias_method :[], :call
38
+
39
+ # @api private
40
+ def graph?
41
+ left.is_a?(Graph)
42
+ end
43
+
44
+ # @api private
45
+ def result
46
+ left.result
47
+ end
48
+
49
+ # @api private
50
+ def decorate?(response)
51
+ super || response.is_a?(Graph)
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rom/command"
4
+
5
+ module ROM
6
+ module Commands
7
+ # Create command
8
+ #
9
+ # This command inserts a new tuple into a relation
10
+ #
11
+ # @abstract
12
+ class Create < Command
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rom/command"
4
+
5
+ module ROM
6
+ module Commands
7
+ # Delete command
8
+ #
9
+ # This command removes tuples from its target relation
10
+ #
11
+ # @abstract
12
+ class Delete < Command
13
+ config.restrictable = true
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rom/commands/graph/input_evaluator"
4
+
5
+ module ROM
6
+ module Commands
7
+ class Graph
8
+ # Class methods for command Graph
9
+ #
10
+ # @api private
11
+ module ClassInterface
12
+ # Build a command graph recursively
13
+ #
14
+ # This is used by `Container#command` when array with options is passed in
15
+ #
16
+ # @param [Registry] registry The command registry from container
17
+ # @param [Array] options The options array
18
+ # @param [Array] path The path for input evaluator proc
19
+ #
20
+ # @return [Graph]
21
+ #
22
+ # @api private
23
+ def build(registry, options, path = EMPTY_ARRAY)
24
+ options.reduce { |spec, other| build_command(registry, spec, other, path) }
25
+ end
26
+
27
+ # @api private
28
+ def build_command(registry, spec, other, path)
29
+ cmd_opts, nodes = other
30
+
31
+ key, relation =
32
+ if spec.is_a?(Hash)
33
+ spec.to_a.first
34
+ else
35
+ [spec, spec]
36
+ end
37
+
38
+ name, opts =
39
+ if cmd_opts.is_a?(Hash)
40
+ cmd_opts.to_a.first
41
+ else
42
+ [cmd_opts]
43
+ end
44
+
45
+ command = registry[relation][name]
46
+ tuple_path = Array[*path] << key
47
+ input_proc = InputEvaluator.build(tuple_path, nodes)
48
+
49
+ command = command.curry(input_proc, opts)
50
+
51
+ if nodes
52
+ if nodes.all? { |node| node.is_a?(Array) }
53
+ command.combine(*nodes.map { |node| build(registry, node, tuple_path) })
54
+ else
55
+ command.combine(build(registry, nodes, tuple_path))
56
+ end
57
+ else
58
+ command
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Commands
5
+ class Graph
6
+ # Evaluator for lazy commands which extracts values for commands from nested hashes
7
+ #
8
+ # @api private
9
+ class InputEvaluator
10
+ include Dry::Equalizer(:tuple_path, :excluded_keys)
11
+
12
+ # @!attribute [r] tuple_path
13
+ # @return [Array<Symbol>] A list of keys pointing to a value inside a hash
14
+ attr_reader :tuple_path
15
+
16
+ # @!attribute [r] excluded_keys
17
+ # @return [Array<Symbol>] A list of keys that should be excluded
18
+ attr_reader :excluded_keys
19
+
20
+ # @!attribute [r] exclude_proc
21
+ # @return [Array<Symbol>] A function that should determine which keys should be excluded
22
+ attr_reader :exclude_proc
23
+
24
+ # Build an input evaluator
25
+ #
26
+ # @param [Array<Symbol>] tuple_path The tuple path
27
+ # @param [Array] nodes
28
+ #
29
+ # @return [InputEvaluator]
30
+ #
31
+ # @api private
32
+ def self.build(tuple_path, nodes)
33
+ new(tuple_path, extract_excluded_keys(nodes))
34
+ end
35
+
36
+ # @api private
37
+ def self.extract_excluded_keys(nodes)
38
+ return unless nodes
39
+
40
+ nodes
41
+ .map { |item| item.is_a?(Array) && item.size > 1 ? item.first : item }
42
+ .compact
43
+ .map { |item| item.is_a?(Hash) ? item.keys.first : item }
44
+ .reject { |item| item.is_a?(Array) }
45
+ end
46
+
47
+ # Return default exclude_proc
48
+ #
49
+ # @api private
50
+ def self.exclude_proc(excluded_keys)
51
+ -> input { input.reject { |k, _| excluded_keys.include?(k) } }
52
+ end
53
+
54
+ # Initialize a new input evaluator
55
+ #
56
+ # @return [InputEvaluator]
57
+ #
58
+ # @api private
59
+ def initialize(tuple_path, excluded_keys)
60
+ @tuple_path = tuple_path
61
+ @excluded_keys = excluded_keys
62
+ @exclude_proc = self.class.exclude_proc(excluded_keys)
63
+ end
64
+
65
+ # Evaluate input hash
66
+ #
67
+ # @param [Hash] input The input hash
68
+ # @param [Integer] index Optional index
69
+ #
70
+ # @return [Hash]
71
+ def call(input, index = nil)
72
+ value =
73
+ begin
74
+ if index
75
+ tuple_path[0..tuple_path.size - 2]
76
+ .reduce(input) { |a, e| a.fetch(e) }
77
+ .at(index)[tuple_path.last]
78
+ else
79
+ tuple_path.reduce(input) { |a, e| a.fetch(e) }
80
+ end
81
+ rescue KeyError => e
82
+ raise KeyMissing, e.message
83
+ end
84
+
85
+ if excluded_keys
86
+ value.is_a?(Array) ? value.map(&exclude_proc) : exclude_proc[value]
87
+ else
88
+ value
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rom/initializer"
4
+ require "rom/pipeline"
5
+ require "rom/commands/graph/class_interface"
6
+
7
+ module ROM
8
+ module Commands
9
+ # Command graph
10
+ #
11
+ # @api private
12
+ class Graph
13
+ extend Initializer
14
+ include Dry::Equalizer(:root, :nodes)
15
+
16
+ extend ClassInterface
17
+
18
+ include Pipeline
19
+ include Pipeline::Proxy
20
+
21
+ # @attr_reader [Command] root The root command
22
+ param :root
23
+
24
+ # @attr_reader [Array<Command>] nodes The child commands
25
+ param :nodes
26
+
27
+ alias_method :left, :root
28
+ alias_method :right, :nodes
29
+
30
+ # @attr_reader [Symbol] root's relation name
31
+ option :name, default: -> { root.name }
32
+
33
+ # Calls root and all nodes with the result from root
34
+ #
35
+ # Graph results are mappable through `combine` operation in mapper DSL
36
+ #
37
+ # @example
38
+ # create_user = rom.commands[:users][:create]
39
+ # create_task = rom.commands[:tasks][:create]
40
+ #
41
+ # command = create_user
42
+ # .curry(name: 'Jane')
43
+ # .combine(create_task.curry(title: 'Task'))
44
+ #
45
+ # command.call
46
+ #
47
+ # @return [Array] nested array with command results
48
+ #
49
+ # @api public
50
+ def call(*args)
51
+ left = root.call(*args)
52
+
53
+ right = nodes.map { |node|
54
+ response =
55
+ if node.lazy?
56
+ node.call(args.first, left)
57
+ else
58
+ node.call(left)
59
+ end
60
+
61
+ if node.one? && !node.graph?
62
+ [response]
63
+ else
64
+ response
65
+ end
66
+ }
67
+
68
+ if one?
69
+ [[left], right]
70
+ else
71
+ [left, right]
72
+ end
73
+ end
74
+
75
+ # @api private
76
+ def graph?
77
+ true
78
+ end
79
+
80
+ private
81
+
82
+ # @api public
83
+ def composite_class
84
+ Command::Composite
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Commands
5
+ class Lazy
6
+ # Lazy command wrapper for create commands
7
+ #
8
+ # @api public
9
+ class Create < Lazy
10
+ # Execute a command
11
+ #
12
+ # @see Command::Create#call
13
+ #
14
+ # @return [Hash,Array<Hash>]
15
+ #
16
+ # @api public
17
+ def call(*args)
18
+ first = args.first
19
+ last = args.last
20
+ size = args.size
21
+
22
+ if size > 1 && last.is_a?(Array)
23
+ last.map.with_index do |parent, index|
24
+ children = evaluator.call(first, index)
25
+ command_proc[command, parent, children].call(children, parent)
26
+ end.reduce(:concat)
27
+ else
28
+ input = evaluator.call(first)
29
+ command.call(input, *args[1..size - 1])
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Commands
5
+ class Lazy
6
+ # Lazy command wrapper for delete commands
7
+ #
8
+ # @api public
9
+ class Delete < Lazy
10
+ # Execute a lazy delete command
11
+ #
12
+ # @see Commands::Delete#call
13
+ #
14
+ # @return [Hash, Array<Hash>]
15
+ #
16
+ # @api public
17
+ def call(*args)
18
+ first = args.first
19
+ last = args.last
20
+ size = args.size
21
+
22
+ if size > 1 && last.is_a?(Array)
23
+ raise NotImplementedError
24
+ else
25
+ input = evaluator.call(first)
26
+
27
+ if input.is_a?(Array)
28
+ input.map do |item|
29
+ command_proc[command, *(size > 1 ? [last, item] : [input])].call
30
+ end
31
+ else
32
+ command_proc[command, input].call
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ module Commands
5
+ class Lazy
6
+ # Lazy command wrapper for update commands
7
+ #
8
+ # @api public
9
+ class Update < Lazy
10
+ # Execute a lazy update command
11
+ #
12
+ # @see Commands::Update#call
13
+ #
14
+ # @return [Hash, Array<Hash>]
15
+ #
16
+ # @api public
17
+ def call(*args)
18
+ first = args.first
19
+ last = args.last
20
+ size = args.size
21
+
22
+ if size > 1 && last.is_a?(Array)
23
+ last.map.with_index do |parent, index|
24
+ children = evaluator.call(first, index)
25
+
26
+ children.map do |child|
27
+ command_proc[command, parent, child].call(child, parent)
28
+ end
29
+ end.reduce(:concat)
30
+ else
31
+ input = evaluator.call(first)
32
+
33
+ if input.is_a?(Array)
34
+ input.map.with_index do |item, _index|
35
+ command_proc[command, last, item].call(item, *args[1..size - 1])
36
+ end
37
+ else
38
+ command_proc[command, *(size > 1 ? [last, input] : [input])]
39
+ .call(input, *args[1..size - 1])
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end