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
@@ -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,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
|