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,203 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/core/memoizable"
4
+
5
+ require_relative "../core"
6
+ require_relative "../constants"
7
+ require_relative "../initializer"
8
+ require_relative "../inferrer"
9
+ require_relative "container"
10
+
11
+ module ROM
12
+ module Registries
13
+ class Root
14
+ extend Initializer
15
+
16
+ include Dry::Core::Memoizable
17
+ include Dry::Effects::Handler.Reader(:registry)
18
+ include Enumerable
19
+
20
+ option :config, default: -> { ROM.config }
21
+
22
+ option :components, default: -> { Components::Registry.new(provider: Setup.new) }
23
+
24
+ option :container, default: -> { Container.new }
25
+
26
+ option :inferrer, default: -> { Inferrer.new }
27
+
28
+ option :loader, optional: true
29
+
30
+ option :type, optional: true
31
+
32
+ option :path, default: -> { EMPTY_ARRAY }
33
+
34
+ option :root, default: -> { self }
35
+
36
+ option :opts, default: -> { EMPTY_HASH }
37
+
38
+ CORE_COMPONENTS.each do |type|
39
+ require_relative type.to_s
40
+
41
+ define_method(type) do |**options|
42
+ registry = scoped(__method__, type: __method__, **options)
43
+
44
+ klass =
45
+ case type
46
+ when :relations then Relations
47
+ when :commands then Commands
48
+ when :mappers then Mappers
49
+ when :datasets then Datasets
50
+ when :schemas then Schemas
51
+ when :views then Views
52
+ when :associations then Associations
53
+ end
54
+
55
+ klass ? klass.new(**registry.options) : registry
56
+ end
57
+
58
+ define_method(:"#{type}?") do
59
+ self.type == type
60
+ end
61
+ end
62
+
63
+ # @api public
64
+ def fetch(key, &block)
65
+ case key
66
+ when Symbol
67
+ fetch("#{namespace}.#{key}", &block)
68
+ when String
69
+ return container[key] if container.key?(key)
70
+
71
+ loader&.auto_load_component_file(type, key)
72
+
73
+ with_registry(root) { build(key, &block) }.tap { |item|
74
+ container.register(key, item)
75
+ }
76
+ when Array
77
+ with_registry(self) { inferrer.call(key, type, **opts) }
78
+ else
79
+ if key.respond_to?(:to_sym)
80
+ fetch(key.to_sym, &block)
81
+ else
82
+ element_not_found(key)
83
+ end
84
+ end
85
+ rescue KeyError
86
+ element_not_found(key)
87
+ end
88
+ alias_method :[], :fetch
89
+
90
+ # @api public
91
+ def infer(id, **options)
92
+ fetch(id) do
93
+ inferred_config = config[handler.key].inherit(**config.component, **options)
94
+ define_component(id: id, **inferred_config).build
95
+ end
96
+ end
97
+
98
+ # @api private
99
+ def provider
100
+ components.provider
101
+ end
102
+
103
+ # @api private
104
+ def provider_type
105
+ config.component.type
106
+ end
107
+
108
+ # @api private
109
+ def handler
110
+ components.handlers[type]
111
+ end
112
+
113
+ # @api private
114
+ def build(key, &block)
115
+ components.(key, &block)
116
+ end
117
+
118
+ # @api private
119
+ memoize def namespace
120
+ path.join(".")
121
+ end
122
+
123
+ # @api private
124
+ memoize def compiler
125
+ inferrer.compiler(type, **opts)
126
+ end
127
+
128
+ # @api private
129
+ memoize def relation_ids
130
+ components.relations.map(&:id)
131
+ end
132
+
133
+ # @api private
134
+ def scoped(*scope, **options)
135
+ with(path: path + scope, **options)
136
+ end
137
+
138
+ # @api private
139
+ def each
140
+ keys.each { |key| yield(fetch(key)) }
141
+ end
142
+
143
+ # @api private
144
+ def plugins
145
+ config.component.plugins
146
+ end
147
+
148
+ # @api private
149
+ def keys
150
+ all = (components.keys + container.keys).uniq
151
+ return all if path.empty?
152
+
153
+ all.select { |key| key.start_with?(namespace) }
154
+ end
155
+
156
+ # @api private
157
+ def ids
158
+ components[type].map(&:id)
159
+ end
160
+
161
+ # @api private
162
+ def key?(key)
163
+ keys.include?("#{namespace}.#{key}")
164
+ end
165
+
166
+ # @api public
167
+ def empty?
168
+ keys.empty?
169
+ end
170
+
171
+ # @api private
172
+ def inspect
173
+ %(#<#{self.class} adapters=#{components.gateways.map(&:adapter)} keys=#{keys}>)
174
+ end
175
+
176
+ # Disconnect all gateways
177
+ #
178
+ # @example
179
+ # rom = ROM.setup(:sql, 'sqlite://my_db.sqlite')
180
+ # rom.relations[:users].insert(name: "Jane")
181
+ # rom.disconnect
182
+ #
183
+ # @return [Hash<Symbol=>Gateway>] a hash with disconnected gateways
184
+ #
185
+ # @api public
186
+ def disconnect
187
+ container.keys.grep(/gateways/).each { |key| self[key].disconnect }
188
+ end
189
+
190
+ private
191
+
192
+ # @api private
193
+ def define_component(**options)
194
+ provider.public_send(handler.key, **options)
195
+ end
196
+
197
+ # @api private
198
+ def element_not_found(key)
199
+ raise MISSING_ELEMENT_ERRORS.fetch(type), key
200
+ end
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "nestable"
4
+
5
+ module ROM
6
+ module Registries
7
+ # @api public
8
+ class Schemas < Root
9
+ prepend Nestable
10
+
11
+ # Resolve relation's canonical schema
12
+ #
13
+ # @param provider [#config]
14
+ # @return [Schema]
15
+ #
16
+ # @api public
17
+ def canonical(provider)
18
+ schema = scoped(provider.config.component.id).fetch(provider.config.component.dataset) {
19
+ fetch(provider.config.component.id)
20
+ }
21
+
22
+ if schema.is_a?(self.class)
23
+ unscoped.fetch(provider.config.component.id)
24
+ else
25
+ schema
26
+ end
27
+ end
28
+
29
+ # @api private
30
+ def unscoped
31
+ root.schemas
32
+ end
33
+
34
+ # @api private
35
+ def define_component(**options)
36
+ return super unless provider_type == :relation
37
+
38
+ comp = components.get(:schemas, relation: config.component.id, abstract: false)
39
+
40
+ comp || super(**options, relation_id: config.component.id)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "nestable"
4
+
5
+ module ROM
6
+ module Registries
7
+ class Views < Root
8
+ prepend Nestable
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "set"
4
+
5
+ require "dry/effects"
6
+
7
+ require "rom/support/inflector"
8
+
9
+ require "rom/constants"
10
+ require "rom/relation/name"
11
+ require "rom/schema"
12
+
13
+ module ROM
14
+ class Relation
15
+ # Global class-level API for relation classes
16
+ #
17
+ # @api public
18
+ module ClassInterface
19
+ extend Notifications::Listener
20
+
21
+ # Return adapter-specific relation subclass
22
+ #
23
+ # @example
24
+ # ROM::Relation[:memory]
25
+ # # => ROM::Memory::Relation
26
+ #
27
+ # @return [Class]
28
+ #
29
+ # @api public
30
+ def [](adapter)
31
+ ROM.adapters.fetch(adapter).const_get(:Relation)
32
+ rescue KeyError
33
+ raise AdapterNotPresentError.new(adapter, :relation)
34
+ end
35
+
36
+ # Dynamically define a method that will forward to the dataset and wrap
37
+ # response in the relation itself
38
+ #
39
+ # @example
40
+ # class SomeAdapterRelation < ROM::Relation
41
+ # forward :super_query
42
+ # end
43
+ #
44
+ # @api public
45
+ def forward(*methods)
46
+ methods.each do |method|
47
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
48
+ def #{method}(*args, &block)
49
+ new(dataset.__send__(:#{method}, *args, &block))
50
+ end
51
+ RUBY
52
+ end
53
+ end
54
+
55
+ # @api private
56
+ def curried
57
+ Curried
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,160 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rom/relation/graph"
4
+ require "rom/relation/commands"
5
+
6
+ module ROM
7
+ class Relation
8
+ # Represents a relation graphs which combines root relation
9
+ # with other relation nodes
10
+ #
11
+ # @api public
12
+ class Combined < Graph
13
+ include Commands
14
+
15
+ # Create a new relation combined with others
16
+ #
17
+ # @param [Relation] relation
18
+ # @param [Array<Relation>] nodes
19
+ #
20
+ # @return [Combined]
21
+ #
22
+ # @api public
23
+ def self.new(relation, nodes)
24
+ struct_ns = relation.options[:struct_namespace]
25
+ new_nodes = nodes.uniq(&:name).map { |node| node.struct_namespace(struct_ns) }
26
+
27
+ root =
28
+ if relation.is_a?(self)
29
+ new_nodes.concat(relation.nodes)
30
+ relation.root
31
+ else
32
+ relation
33
+ end
34
+
35
+ super(root, new_nodes)
36
+ end
37
+
38
+ # Combine this graph with more nodes
39
+ #
40
+ # @param [Array<Relation>] others A list of relations
41
+ #
42
+ # @return [Graph]
43
+ #
44
+ # @api public
45
+ def combine_with(*others)
46
+ self.class.new(root, nodes + others)
47
+ end
48
+
49
+ # Combine with other relations
50
+ #
51
+ # @see Relation#combine
52
+ #
53
+ # @return [Combined]
54
+ #
55
+ # @api public
56
+ def combine(*args)
57
+ self.class.new(root, nodes + root.combine(*args).nodes)
58
+ end
59
+
60
+ # Materialize combined relation
61
+ #
62
+ # @return [Loaded]
63
+ #
64
+ # @api public
65
+ def call(*args)
66
+ left = root.with(auto_map: false, auto_struct: false).call(*args)
67
+
68
+ right =
69
+ if left.empty?
70
+ nodes.map { |node| Loaded.new(node, EMPTY_ARRAY) }
71
+ else
72
+ nodes.map { |node| node.call(left) }
73
+ end
74
+
75
+ if auto_map?
76
+ Loaded.new(self, mapper.([left, right]))
77
+ else
78
+ Loaded.new(self, [left, right])
79
+ end
80
+ end
81
+
82
+ # Return a new combined relation with adjusted node returned from a block
83
+ #
84
+ # @example with a node identifier
85
+ # combine(:tasks).node(:tasks) { |tasks| tasks.prioritized }
86
+ #
87
+ # @example with a nested path
88
+ # combine(tasks: :tags).node(tasks: :tags) { |tags| tags.where(name: 'red') }
89
+ #
90
+ # @param [Symbol] name The node relation name
91
+ #
92
+ # @yieldparam [Relation] relation The relation node
93
+ # @yieldreturn [Relation] The new relation node
94
+ #
95
+ # @return [Relation]
96
+ #
97
+ # @api public
98
+ def node(name, &block)
99
+ if name.is_a?(Symbol) && !nodes.map { |n| n.name.key }.include?(name)
100
+ raise ArgumentError, "#{name.inspect} is not a valid aggregate node name"
101
+ end
102
+
103
+ new_nodes = nodes.map { |node|
104
+ case name
105
+ when Symbol
106
+ name == node.name.key ? yield(node) : node
107
+ when Hash
108
+ other, *rest = name.flatten(1)
109
+ if other == node.name.key
110
+ nodes.detect { |n| n.name.key == other }.node(*rest, &block)
111
+ else
112
+ node
113
+ end
114
+ else
115
+ node
116
+ end
117
+ }
118
+
119
+ with_nodes(new_nodes)
120
+ end
121
+
122
+ # Return a `:create` command that can insert data from a nested hash.
123
+ #
124
+ # This is limited to `:create` commands only, because automatic restriction
125
+ # for `:update` commands would be quite complex. It's possible that in the
126
+ # future support for `:update` commands will be added though.
127
+ #
128
+ # Another limitation is that it can only work when you're composing
129
+ # parent and its child(ren), which follows canonical hierarchy from your
130
+ # database, so that parents are created first, then their PKs are set
131
+ # as FKs in child tuples. It should be possible to make it work with
132
+ # both directions (parent => child or child => parent), and it would
133
+ # require converting input tuples based on how they depend on each other,
134
+ # which we could do in the future.
135
+ #
136
+ # Expanding functionality of this method is planned for rom 5.0.
137
+ #
138
+ # @see Relation#command
139
+ #
140
+ # @raise NotImplementedError when type is not `:create`
141
+ #
142
+ # @api public
143
+ def command(type, *args)
144
+ if type == :create
145
+ super
146
+ else
147
+ raise NotImplementedError,
148
+ "#{self.class}#command doesn't work with #{type.inspect} command type yet"
149
+ end
150
+ end
151
+
152
+ private
153
+
154
+ # @api private
155
+ def decorate?(other)
156
+ super || other.is_a?(self.class) || other.is_a?(Wrap)
157
+ end
158
+ end
159
+ end
160
+ end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ROM
4
+ class Relation
5
+ # Extensions for relation classes which provide access to commands
6
+ #
7
+ # @api public
8
+ module Commands
9
+ # Return a command for the relation
10
+ #
11
+ # This method can either return an existing custom command identified
12
+ # by `type` param, or generate a command dynamically based on relation
13
+ # AST.
14
+ #
15
+ # @example build a simple :create command
16
+ # users.command(:create)
17
+ #
18
+ # @example build a command which returns multiple results
19
+ # users.command(:create, result: many)
20
+ #
21
+ # @example build a command which uses a specific plugin
22
+ # users.command(:create, use: :timestamps)
23
+ #
24
+ # @example build a command which sends results through a custom mapper
25
+ # users.command(:create, mapper: :my_mapper_identifier)
26
+ #
27
+ # @example return an existing custom command
28
+ # users.command(:my_custom_command_identifier)
29
+ #
30
+ # @param type [Symbol] The command type (:create, :update or :delete)
31
+ # @param opts [Hash] Additional options
32
+ # @option opts [Symbol] :mapper (nil) An optional mapper applied to the command result
33
+ # @option opts [Array<Symbol>] :use ([]) A list of command plugins
34
+ # @option opts [Symbol] :result (:one) Set how many results the command should return.
35
+ # Can be `:one` or `:many`
36
+ #
37
+ # @return [ROM::Command]
38
+ #
39
+ # @api public
40
+ def command(type, mapper: nil, use: EMPTY_ARRAY, plugins_options: EMPTY_HASH, **opts)
41
+ base_command =
42
+ if commands.key?(type)
43
+ commands[type]
44
+ else
45
+ commands[[type, adapter, to_ast, use, plugins_options, opts]]
46
+ end
47
+
48
+ command =
49
+ if mapper
50
+ base_command >> mappers[mapper]
51
+ elsif auto_map?
52
+ base_command >> self.mapper
53
+ else
54
+ base_command
55
+ end
56
+
57
+ if command.restrictible?
58
+ command.new(self)
59
+ else
60
+ command
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rom/relation/loaded"
4
+ require "rom/relation/materializable"
5
+ require "rom/pipeline"
6
+
7
+ module ROM
8
+ class Relation
9
+ # Left-to-right relation composition used for data-pipelining
10
+ #
11
+ # @api public
12
+ class Composite < Pipeline::Composite
13
+ include Materializable
14
+
15
+ # Call the pipeline by passing results from left to right
16
+ #
17
+ # Optional args are passed to the left object
18
+ #
19
+ # @return [Loaded]
20
+ #
21
+ # @api public
22
+ def call(*args)
23
+ relation = left.call(*args)
24
+ response = right.call(relation)
25
+
26
+ if response.is_a?(Loaded)
27
+ response
28
+ else
29
+ relation.new(response)
30
+ end
31
+ end
32
+ alias_method :[], :call
33
+
34
+ # @see Relation#map_to
35
+ #
36
+ # @api public
37
+ def map_to(klass)
38
+ self >> left.map_to(klass).mapper
39
+ end
40
+
41
+ private
42
+
43
+ # @api private
44
+ #
45
+ # @see Pipeline::Proxy#decorate?
46
+ #
47
+ # @api private
48
+ def decorate?(response)
49
+ super || response.is_a?(Graph)
50
+ end
51
+ end
52
+ end
53
+ end