rom 5.4.1 → 6.0.0.alpha1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +58 -65
- data/LICENSE +1 -1
- data/README.md +7 -6
- data/lib/rom/array_dataset.rb +46 -0
- data/lib/rom/associations/abstract.rb +217 -0
- data/lib/rom/associations/definitions/abstract.rb +150 -0
- data/lib/rom/associations/definitions/many_to_many.rb +29 -0
- data/lib/rom/associations/definitions/many_to_one.rb +14 -0
- data/lib/rom/associations/definitions/one_to_many.rb +14 -0
- data/lib/rom/associations/definitions/one_to_one.rb +14 -0
- data/lib/rom/associations/definitions/one_to_one_through.rb +14 -0
- data/lib/rom/associations/definitions.rb +7 -0
- data/lib/rom/associations/many_to_many.rb +128 -0
- data/lib/rom/associations/many_to_one.rb +65 -0
- data/lib/rom/associations/one_to_many.rb +65 -0
- data/lib/rom/associations/one_to_one.rb +13 -0
- data/lib/rom/associations/one_to_one_through.rb +13 -0
- data/lib/rom/associations/through_identifier.rb +41 -0
- data/lib/rom/attribute.rb +425 -0
- data/lib/rom/auto_curry.rb +70 -0
- data/lib/rom/cache.rb +87 -0
- data/lib/rom/changeset/associated.rb +110 -0
- data/lib/rom/changeset/create.rb +18 -0
- data/lib/rom/changeset/delete.rb +15 -0
- data/lib/rom/changeset/extensions/relation.rb +26 -0
- data/lib/rom/changeset/pipe.rb +81 -0
- data/lib/rom/changeset/pipe_registry.rb +27 -0
- data/lib/rom/changeset/stateful.rb +285 -0
- data/lib/rom/changeset/update.rb +81 -0
- data/lib/rom/changeset.rb +185 -0
- data/lib/rom/command.rb +351 -0
- data/lib/rom/command_compiler.rb +201 -0
- data/lib/rom/command_proxy.rb +36 -0
- data/lib/rom/commands/class_interface.rb +236 -0
- data/lib/rom/commands/composite.rb +55 -0
- data/lib/rom/commands/create.rb +15 -0
- data/lib/rom/commands/delete.rb +16 -0
- data/lib/rom/commands/graph/class_interface.rb +64 -0
- data/lib/rom/commands/graph/input_evaluator.rb +94 -0
- data/lib/rom/commands/graph.rb +88 -0
- data/lib/rom/commands/lazy/create.rb +35 -0
- data/lib/rom/commands/lazy/delete.rb +39 -0
- data/lib/rom/commands/lazy/update.rb +46 -0
- data/lib/rom/commands/lazy.rb +106 -0
- data/lib/rom/commands/update.rb +16 -0
- data/lib/rom/commands.rb +5 -0
- data/lib/rom/compat/auto_registration.rb +115 -0
- data/lib/rom/compat/auto_registration_strategies/base.rb +29 -0
- data/lib/rom/compat/auto_registration_strategies/custom_namespace.rb +84 -0
- data/lib/rom/compat/auto_registration_strategies/no_namespace.rb +33 -0
- data/lib/rom/compat/auto_registration_strategies/with_namespace.rb +29 -0
- data/lib/rom/compat/command.rb +74 -0
- data/lib/rom/compat/components/dsl/schema.rb +130 -0
- data/lib/rom/compat/components.rb +91 -0
- data/lib/rom/compat/global.rb +17 -0
- data/lib/rom/compat/mapper.rb +22 -0
- data/lib/rom/compat/registries.rb +47 -0
- data/lib/rom/compat/relation.rb +40 -0
- data/lib/rom/compat/schema/dsl.rb +260 -0
- data/lib/rom/compat/setting_proxy.rb +44 -0
- data/lib/rom/compat/setup.rb +151 -0
- data/lib/rom/compat/transformer.rb +49 -0
- data/lib/rom/compat.rb +22 -0
- data/lib/rom/components/association.rb +26 -0
- data/lib/rom/components/command.rb +24 -0
- data/lib/rom/components/core.rb +148 -0
- data/lib/rom/components/dataset.rb +60 -0
- data/lib/rom/components/dsl/association.rb +47 -0
- data/lib/rom/components/dsl/command.rb +60 -0
- data/lib/rom/components/dsl/core.rb +126 -0
- data/lib/rom/components/dsl/dataset.rb +33 -0
- data/lib/rom/components/dsl/gateway.rb +14 -0
- data/lib/rom/components/dsl/mapper.rb +70 -0
- data/lib/rom/components/dsl/relation.rb +49 -0
- data/lib/rom/components/dsl/schema.rb +150 -0
- data/lib/rom/components/dsl/view.rb +82 -0
- data/lib/rom/components/dsl.rb +255 -0
- data/lib/rom/components/gateway.rb +50 -0
- data/lib/rom/components/mapper.rb +29 -0
- data/lib/rom/components/provider.rb +160 -0
- data/lib/rom/components/registry.rb +154 -0
- data/lib/rom/components/relation.rb +41 -0
- data/lib/rom/components/schema.rb +61 -0
- data/lib/rom/components/view.rb +55 -0
- data/lib/rom/components.rb +55 -0
- data/lib/rom/configuration_dsl.rb +4 -0
- data/lib/rom/constants.rb +135 -0
- data/lib/rom/container.rb +182 -0
- data/lib/rom/core.rb +125 -0
- data/lib/rom/data_proxy.rb +97 -0
- data/lib/rom/enumerable_dataset.rb +70 -0
- data/lib/rom/gateway.rb +232 -0
- data/lib/rom/global.rb +56 -0
- data/lib/rom/header/attribute.rb +190 -0
- data/lib/rom/header.rb +198 -0
- data/lib/rom/inferrer.rb +55 -0
- data/lib/rom/initializer.rb +80 -0
- data/lib/rom/lint/enumerable_dataset.rb +56 -0
- data/lib/rom/lint/gateway.rb +120 -0
- data/lib/rom/lint/linter.rb +79 -0
- data/lib/rom/lint/spec.rb +22 -0
- data/lib/rom/lint/test.rb +98 -0
- data/lib/rom/loader.rb +161 -0
- data/lib/rom/mapper/attribute_dsl.rb +480 -0
- data/lib/rom/mapper/dsl.rb +107 -0
- data/lib/rom/mapper/model_dsl.rb +61 -0
- data/lib/rom/mapper.rb +99 -0
- data/lib/rom/mapper_compiler.rb +84 -0
- data/lib/rom/memory/associations/many_to_many.rb +12 -0
- data/lib/rom/memory/associations/many_to_one.rb +12 -0
- data/lib/rom/memory/associations/one_to_many.rb +12 -0
- data/lib/rom/memory/associations/one_to_one.rb +12 -0
- data/lib/rom/memory/associations.rb +6 -0
- data/lib/rom/memory/commands.rb +60 -0
- data/lib/rom/memory/dataset.rb +127 -0
- data/lib/rom/memory/gateway.rb +66 -0
- data/lib/rom/memory/mapper_compiler.rb +10 -0
- data/lib/rom/memory/relation.rb +91 -0
- data/lib/rom/memory/schema.rb +32 -0
- data/lib/rom/memory/storage.rb +61 -0
- data/lib/rom/memory/types.rb +11 -0
- data/lib/rom/memory.rb +7 -0
- data/lib/rom/model_builder.rb +103 -0
- data/lib/rom/open_struct.rb +112 -0
- data/lib/rom/pipeline.rb +111 -0
- data/lib/rom/plugin.rb +130 -0
- data/lib/rom/plugins/class_methods.rb +37 -0
- data/lib/rom/plugins/command/schema.rb +45 -0
- data/lib/rom/plugins/command/timestamps.rb +149 -0
- data/lib/rom/plugins/dsl.rb +53 -0
- data/lib/rom/plugins/relation/changeset.rb +97 -0
- data/lib/rom/plugins/relation/instrumentation.rb +66 -0
- data/lib/rom/plugins/relation/registry_reader.rb +36 -0
- data/lib/rom/plugins/schema/timestamps.rb +59 -0
- data/lib/rom/plugins.rb +100 -0
- data/lib/rom/processor/composer.rb +37 -0
- data/lib/rom/processor/transformer.rb +415 -0
- data/lib/rom/processor.rb +30 -0
- data/lib/rom/registries/associations.rb +26 -0
- data/lib/rom/registries/commands.rb +11 -0
- data/lib/rom/registries/container.rb +12 -0
- data/lib/rom/registries/datasets.rb +21 -0
- data/lib/rom/registries/gateways.rb +8 -0
- data/lib/rom/registries/mappers.rb +21 -0
- data/lib/rom/registries/nestable.rb +32 -0
- data/lib/rom/registries/relations.rb +8 -0
- data/lib/rom/registries/root.rb +203 -0
- data/lib/rom/registries/schemas.rb +44 -0
- data/lib/rom/registries/views.rb +11 -0
- data/lib/rom/relation/class_interface.rb +61 -0
- data/lib/rom/relation/combined.rb +160 -0
- data/lib/rom/relation/commands.rb +65 -0
- data/lib/rom/relation/composite.rb +53 -0
- data/lib/rom/relation/curried.rb +129 -0
- data/lib/rom/relation/graph.rb +107 -0
- data/lib/rom/relation/loaded.rb +136 -0
- data/lib/rom/relation/materializable.rb +62 -0
- data/lib/rom/relation/name.rb +122 -0
- data/lib/rom/relation/wrap.rb +64 -0
- data/lib/rom/relation.rb +625 -0
- data/lib/rom/repository/class_interface.rb +162 -0
- data/lib/rom/repository/relation_reader.rb +48 -0
- data/lib/rom/repository/root.rb +75 -0
- data/lib/rom/repository/session.rb +60 -0
- data/lib/rom/repository.rb +179 -0
- data/lib/rom/schema/associations_dsl.rb +222 -0
- data/lib/rom/schema/inferrer.rb +106 -0
- data/lib/rom/schema.rb +471 -0
- data/lib/rom/settings.rb +141 -0
- data/lib/rom/setup.rb +297 -0
- data/lib/rom/struct.rb +99 -0
- data/lib/rom/struct_compiler.rb +114 -0
- data/lib/rom/support/configurable.rb +213 -0
- data/lib/rom/support/inflector.rb +31 -0
- data/lib/rom/support/memoizable.rb +61 -0
- data/lib/rom/support/notifications.rb +238 -0
- data/lib/rom/transaction.rb +26 -0
- data/lib/rom/transformer.rb +46 -0
- data/lib/rom/types.rb +74 -0
- data/lib/rom/version.rb +1 -1
- data/lib/rom-changeset.rb +4 -0
- data/lib/rom-core.rb +3 -0
- data/lib/rom-repository.rb +4 -0
- data/lib/rom.rb +3 -3
- metadata +302 -23
data/lib/rom/setup.rb
ADDED
@@ -0,0 +1,297 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/equalizer"
|
4
|
+
|
5
|
+
require "rom/support/inflector"
|
6
|
+
|
7
|
+
require "rom/core"
|
8
|
+
require "rom/components/provider"
|
9
|
+
|
10
|
+
require "rom/open_struct"
|
11
|
+
require "rom/constants"
|
12
|
+
require "rom/gateway"
|
13
|
+
require "rom/loader"
|
14
|
+
|
15
|
+
module ROM
|
16
|
+
# @api public
|
17
|
+
class Setup
|
18
|
+
include ROM::Provider(
|
19
|
+
:gateway,
|
20
|
+
:dataset,
|
21
|
+
:schema,
|
22
|
+
:relation,
|
23
|
+
:association,
|
24
|
+
:mapper,
|
25
|
+
:command,
|
26
|
+
:plugin,
|
27
|
+
type: :component
|
28
|
+
)
|
29
|
+
|
30
|
+
DEFAULT_CLASS_NAMESPACE = "ROM"
|
31
|
+
|
32
|
+
CLASS_NAME_INFERRERS = {
|
33
|
+
relation: -> (name, type:, inflector:, class_namespace:, **) {
|
34
|
+
[
|
35
|
+
class_namespace,
|
36
|
+
inflector.pluralize(inflector.camelize(type)),
|
37
|
+
inflector.camelize(name)
|
38
|
+
].compact.join("::")
|
39
|
+
},
|
40
|
+
command: -> (name, inflector:, adapter:, command_type:, class_namespace:, **) {
|
41
|
+
[
|
42
|
+
class_namespace,
|
43
|
+
inflector.classify(adapter),
|
44
|
+
"Commands",
|
45
|
+
"#{command_type}[#{inflector.pluralize(inflector.classify(name))}]"
|
46
|
+
].join("::")
|
47
|
+
}
|
48
|
+
}.freeze
|
49
|
+
|
50
|
+
DEFAULT_CLASS_NAME_INFERRER = -> (name, type:, **opts) {
|
51
|
+
CLASS_NAME_INFERRERS.fetch(type).(name, type: type, **opts)
|
52
|
+
}.freeze
|
53
|
+
|
54
|
+
# Global defaults
|
55
|
+
setting :inflector, default: Inflector, reader: true
|
56
|
+
|
57
|
+
setting :gateways, default: EMPTY_HASH
|
58
|
+
|
59
|
+
setting :class_name_inferrer, default: DEFAULT_CLASS_NAME_INFERRER, reader: true
|
60
|
+
|
61
|
+
setting :class_namespace, default: DEFAULT_CLASS_NAMESPACE, reader: true
|
62
|
+
|
63
|
+
setting :auto_register do
|
64
|
+
setting :root_directory
|
65
|
+
setting :auto_load
|
66
|
+
setting :namespace
|
67
|
+
setting :component_dirs, default: {
|
68
|
+
relations: :relations, mappers: :mappers, commands: :commands
|
69
|
+
}
|
70
|
+
setting :inflector, default: Inflector
|
71
|
+
end
|
72
|
+
|
73
|
+
# Initialize a new configuration
|
74
|
+
#
|
75
|
+
# @return [Configuration]
|
76
|
+
#
|
77
|
+
# @api private
|
78
|
+
def initialize(...)
|
79
|
+
super()
|
80
|
+
configure(...)
|
81
|
+
end
|
82
|
+
|
83
|
+
# @return [Registry::Root] Setup component registry
|
84
|
+
# @api private
|
85
|
+
def registry
|
86
|
+
@registry ||=
|
87
|
+
begin
|
88
|
+
options = registry_options
|
89
|
+
options[:loader] = loader if config.auto_register.auto_load
|
90
|
+
|
91
|
+
super(**options)
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
# This is called internally when you pass a block to ROM.container
|
96
|
+
#
|
97
|
+
# @api private
|
98
|
+
def configure(*args)
|
99
|
+
# Load config from the arguments passed to the constructor.
|
100
|
+
# This *may* override defaults and it's a feature.
|
101
|
+
infer_config(*args) unless args.empty?
|
102
|
+
|
103
|
+
# Load adapters explicitly here to ensure their plugins are present for later use
|
104
|
+
load_adapters
|
105
|
+
|
106
|
+
# Allow customizations now
|
107
|
+
yield(self, config) if block_given?
|
108
|
+
|
109
|
+
# Register gateway components based on current config
|
110
|
+
register_gateways
|
111
|
+
|
112
|
+
self
|
113
|
+
end
|
114
|
+
|
115
|
+
# Enable auto-registration
|
116
|
+
#
|
117
|
+
# @param [String, Pathname] directory The root path to components
|
118
|
+
# @param [Hash] options
|
119
|
+
# @option options [Boolean,String] :namespace Toggle root namespace
|
120
|
+
# @option options [Boolean] :auto_load Toggle auto-loading via Zeitwerk
|
121
|
+
#
|
122
|
+
# @return [Configuration]
|
123
|
+
#
|
124
|
+
# @api public
|
125
|
+
def auto_register(directory, **options)
|
126
|
+
config.auto_register.update(root_directory: directory, **options)
|
127
|
+
self
|
128
|
+
end
|
129
|
+
|
130
|
+
# @api private
|
131
|
+
def register_constant(type, constant)
|
132
|
+
if config.key?(constant.config.component.type)
|
133
|
+
parent_config = config[constant.config.component.type]
|
134
|
+
const_config = constant.config.component
|
135
|
+
|
136
|
+
const_config.inherit!(parent_config).join!(parent_config)
|
137
|
+
|
138
|
+
# TODO: make this work with all components
|
139
|
+
if const_config.key?(:infer_id_from_class) && const_config.infer_id_from_class
|
140
|
+
const_config.id = const_config.inflector.component_id(constant.name)&.to_sym
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
components.add(type, constant: constant, config: constant.config.component)
|
145
|
+
end
|
146
|
+
|
147
|
+
# Register relation class(es) explicitly
|
148
|
+
#
|
149
|
+
# @param [Array<Relation>] *klasses One or more relation classes
|
150
|
+
#
|
151
|
+
# @api public
|
152
|
+
def register_relation(*klasses)
|
153
|
+
klasses.each do |klass|
|
154
|
+
register_constant(:relations, klass)
|
155
|
+
end
|
156
|
+
|
157
|
+
components.relations
|
158
|
+
end
|
159
|
+
|
160
|
+
# Register mapper class(es) explicitly
|
161
|
+
#
|
162
|
+
# @param [Array] *klasses One or more mapper classes
|
163
|
+
#
|
164
|
+
# @api public
|
165
|
+
def register_mapper(*klasses)
|
166
|
+
klasses.each do |klass|
|
167
|
+
register_constant(:mappers, klass)
|
168
|
+
end
|
169
|
+
|
170
|
+
components[:mappers]
|
171
|
+
end
|
172
|
+
|
173
|
+
# Register command class(es) explicitly
|
174
|
+
#
|
175
|
+
# @param [Array] *klasses One or more command classes
|
176
|
+
#
|
177
|
+
# @api public
|
178
|
+
def register_command(*klasses)
|
179
|
+
klasses.each do |klass|
|
180
|
+
register_constant(:commands, klass)
|
181
|
+
end
|
182
|
+
|
183
|
+
components.commands
|
184
|
+
end
|
185
|
+
|
186
|
+
# This is called automatically in configure block
|
187
|
+
#
|
188
|
+
# After finalization it is no longer possible to alter the configuration
|
189
|
+
#
|
190
|
+
# @api private
|
191
|
+
def finalize
|
192
|
+
# No more config changes allowed
|
193
|
+
config.freeze
|
194
|
+
yield if block_given?
|
195
|
+
loader.() if config.auto_register.key?(:root_directory)
|
196
|
+
registry
|
197
|
+
end
|
198
|
+
|
199
|
+
# Apply a plugin to the configuration
|
200
|
+
#
|
201
|
+
# @param [Mixed] plugin The plugin identifier, usually a Symbol
|
202
|
+
# @param [Hash] options Plugin options
|
203
|
+
#
|
204
|
+
# @return [Configuration]
|
205
|
+
#
|
206
|
+
# @api public
|
207
|
+
def use(plugin, options = {})
|
208
|
+
case plugin
|
209
|
+
when Array then plugin.each { |p| use(p) }
|
210
|
+
when Hash then plugin.to_a.each { |p| use(*p) }
|
211
|
+
else
|
212
|
+
plugin_registry[:configuration].fetch(plugin).apply_to(self, options)
|
213
|
+
end
|
214
|
+
|
215
|
+
self
|
216
|
+
end
|
217
|
+
|
218
|
+
private
|
219
|
+
|
220
|
+
# @api private
|
221
|
+
def plugin_registry
|
222
|
+
ROM.plugins
|
223
|
+
end
|
224
|
+
|
225
|
+
# This register gateway components based on the configuration
|
226
|
+
#
|
227
|
+
# It is private unlike the rest of register_ methods because
|
228
|
+
# it's called automatically doing configuration phase
|
229
|
+
#
|
230
|
+
# @api private
|
231
|
+
def register_gateways
|
232
|
+
config.gateways.each do |id, gateway_config|
|
233
|
+
base = gateway_config.to_h
|
234
|
+
keys = base.keys - config.gateway.keys
|
235
|
+
args = base[:args] || EMPTY_ARRAY
|
236
|
+
opts = keys.zip(base.values_at(*keys)).to_h
|
237
|
+
|
238
|
+
gateway(id, **base, args: args, opts: opts)
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
# This infers config using arguments passed to the constructor
|
243
|
+
#
|
244
|
+
# @api private
|
245
|
+
def infer_config(*args)
|
246
|
+
config.gateways = ROM::OpenStruct.new
|
247
|
+
|
248
|
+
gateways_config = args.first.is_a?(Hash) ? args.first : {default: args}
|
249
|
+
|
250
|
+
gateways_config.each do |name, value|
|
251
|
+
args = Array(value)
|
252
|
+
|
253
|
+
adapter, *rest = args
|
254
|
+
|
255
|
+
options =
|
256
|
+
if rest.size > 1 && rest.last.is_a?(Hash)
|
257
|
+
{adapter: adapter, args: rest[0..], **rest.last}
|
258
|
+
else
|
259
|
+
options = rest.first.is_a?(Hash) ? rest.first : {args: rest.flatten(1)}
|
260
|
+
{adapter: adapter, **options}
|
261
|
+
end
|
262
|
+
|
263
|
+
config.gateways[name] = ROM::OpenStruct.new(options)
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
# @api private
|
268
|
+
def load_adapters
|
269
|
+
config.gateways.map { |key| config.gateways[key] }.map(&:adapter).uniq do |adapter|
|
270
|
+
Gateway.class_from_symbol(adapter)
|
271
|
+
rescue AdapterLoadError
|
272
|
+
# TODO: we probably want to remove this. It's perfectly fine to have an adapter
|
273
|
+
# defined in another location. Auto-require was done for convenience but
|
274
|
+
# making it mandatory to have that file seems odd now.
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
278
|
+
# @api private
|
279
|
+
def plugins
|
280
|
+
config.component.plugins
|
281
|
+
end
|
282
|
+
|
283
|
+
# @api private
|
284
|
+
def loader
|
285
|
+
@loader ||= Loader.new(
|
286
|
+
config.auto_register.root_directory,
|
287
|
+
components: components,
|
288
|
+
**config.auto_register
|
289
|
+
)
|
290
|
+
end
|
291
|
+
|
292
|
+
# @api private
|
293
|
+
def registry_options
|
294
|
+
{config: config}
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
data/lib/rom/struct.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/struct"
|
4
|
+
|
5
|
+
module ROM
|
6
|
+
# Simple data-struct class
|
7
|
+
#
|
8
|
+
# ROM structs are plain data structures loaded by repositories.
|
9
|
+
# They implement Hash protocol which means that they can be used
|
10
|
+
# in places where Hash-like objects are supported.
|
11
|
+
#
|
12
|
+
# Repositories define subclasses of ROM::Struct automatically, they are
|
13
|
+
# defined in the ROM::Struct namespace by default, but you set it up
|
14
|
+
# to use your namespace/module as well.
|
15
|
+
#
|
16
|
+
# Structs are based on dry-struct gem, they include `schema` with detailed information
|
17
|
+
# about attribute types returned from relations, thus can be introspected to build
|
18
|
+
# additional functionality when desired.
|
19
|
+
#
|
20
|
+
# There is a caveat you should know about when working with structs. Struct classes
|
21
|
+
# have names but at the same time they're anonymous, i.e. you can't get the User struct class
|
22
|
+
# with ROM::Struct::User. ROM will create as many struct classes for User as needed,
|
23
|
+
# they all will have the same name and ROM::Struct::User will be the common parent class for
|
24
|
+
# them. Combined with the ability to provide your own namespace for structs this enables to
|
25
|
+
# pre-define the parent class.
|
26
|
+
#
|
27
|
+
# @example accessing relation struct model
|
28
|
+
# rom = ROM.setup(:sql, 'sqlite::memory') do |conf|
|
29
|
+
# conf.default.create_table(:users) do
|
30
|
+
# primary_key :id
|
31
|
+
# column :name, String
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# class UserRepo < ROM::Repository[:users]
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# user_repo = UserRepo.new(rom)
|
39
|
+
#
|
40
|
+
# # get auto-generated User struct
|
41
|
+
# model = user_repo.users.mapper.model
|
42
|
+
# # => ROM::Struct::User
|
43
|
+
#
|
44
|
+
# # see struct's schema attributes
|
45
|
+
#
|
46
|
+
# # model.schema.key(:id)
|
47
|
+
# # => #<Dry::Types[id: Nominal<Integer meta={primary_key: true, source: :users}>]>
|
48
|
+
#
|
49
|
+
# model.schema[:name]
|
50
|
+
# # => #<Dry::Types[name: Sum<Nominal<NilClass> | Nominal<String> meta={source: :users}>]>
|
51
|
+
#
|
52
|
+
# @example passing a namespace with an existing parent class
|
53
|
+
# module Entities
|
54
|
+
# class User < ROM::Struct
|
55
|
+
# def upcased_name
|
56
|
+
# name.upcase
|
57
|
+
# end
|
58
|
+
# end
|
59
|
+
# end
|
60
|
+
#
|
61
|
+
# class UserRepo < ROM::Repository[:users]
|
62
|
+
# struct_namespace Entities
|
63
|
+
# end
|
64
|
+
#
|
65
|
+
# user_repo = UserRepo.new(rom)
|
66
|
+
# user = user_repo.users.by_pk(1).one!
|
67
|
+
# user.name # => "Jane"
|
68
|
+
# user.upcased_name # => "JANE"
|
69
|
+
#
|
70
|
+
# @see http://dry-rb.org/gems/dry-struct dry-struct
|
71
|
+
# @see http://dry-rb.org/gems/dry-types dry-types
|
72
|
+
#
|
73
|
+
# @api public
|
74
|
+
class Struct < Dry::Struct
|
75
|
+
MissingAttribute = Class.new(NameError)
|
76
|
+
|
77
|
+
# Return attribute value
|
78
|
+
#
|
79
|
+
# @param [Symbol] name The attribute name
|
80
|
+
#
|
81
|
+
# @api public
|
82
|
+
def fetch(name)
|
83
|
+
__send__(name)
|
84
|
+
end
|
85
|
+
|
86
|
+
# @api private
|
87
|
+
def respond_to_missing?(*)
|
88
|
+
super
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def method_missing(*)
|
94
|
+
super
|
95
|
+
rescue NameError => e
|
96
|
+
raise MissingAttribute, "#{e.message} (attribute not loaded?)"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rom/support/inflector"
|
4
|
+
require "dry/core/class_builder"
|
5
|
+
require "dry/types/compiler"
|
6
|
+
|
7
|
+
require "rom/initializer"
|
8
|
+
require "rom/types"
|
9
|
+
require "rom/cache"
|
10
|
+
require "rom/struct"
|
11
|
+
require "rom/open_struct"
|
12
|
+
require "rom/attribute"
|
13
|
+
|
14
|
+
module ROM
|
15
|
+
# @api private
|
16
|
+
class StructCompiler < Dry::Types::Compiler
|
17
|
+
extend Initializer
|
18
|
+
|
19
|
+
param :registry, default: -> { Dry::Types }
|
20
|
+
|
21
|
+
option :cache, default: -> { Cache.new }
|
22
|
+
|
23
|
+
option :inflector, reader: true, default: -> { Inflector }
|
24
|
+
|
25
|
+
# @api private
|
26
|
+
def initialize(*)
|
27
|
+
super
|
28
|
+
@cache = cache.namespaced(:structs)
|
29
|
+
end
|
30
|
+
ruby2_keywords(:initialize) if respond_to?(:ruby2_keywords, true)
|
31
|
+
|
32
|
+
# Build a struct class based on relation header ast
|
33
|
+
#
|
34
|
+
# @api private
|
35
|
+
def call(*args)
|
36
|
+
cache.fetch_or_store(args) do
|
37
|
+
name, header, ns = args
|
38
|
+
attributes = header.map(&method(:visit)).compact
|
39
|
+
|
40
|
+
if attributes.empty?
|
41
|
+
ROM::OpenStruct
|
42
|
+
else
|
43
|
+
build_class(name, ROM::Struct, ns) do |klass|
|
44
|
+
klass.attributes(attributes.to_h)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
alias_method :[], :call
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# @api private
|
54
|
+
def visit_relation(node)
|
55
|
+
_, header, meta = node
|
56
|
+
name = meta[:combine_name] || meta[:alias]
|
57
|
+
namespace = meta.fetch(:struct_namespace)
|
58
|
+
|
59
|
+
model = meta[:model] || call(name, header, namespace)
|
60
|
+
|
61
|
+
member =
|
62
|
+
if model < Dry::Struct
|
63
|
+
model
|
64
|
+
else
|
65
|
+
Dry::Types::Nominal.new(model).constructor(&model.method(:new))
|
66
|
+
end
|
67
|
+
|
68
|
+
if meta[:combine_type] == :many
|
69
|
+
[name, Types::Array.of(member)]
|
70
|
+
else
|
71
|
+
[name, member.optional]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# @api private
|
76
|
+
def visit_attribute(node)
|
77
|
+
name, type, meta = node
|
78
|
+
|
79
|
+
[meta[:alias] && !meta[:wrapped] ? meta[:alias] : name, visit(type).meta(meta)]
|
80
|
+
end
|
81
|
+
|
82
|
+
# @api private
|
83
|
+
def visit_constructor(node)
|
84
|
+
definition, * = node
|
85
|
+
|
86
|
+
visit(definition)
|
87
|
+
end
|
88
|
+
|
89
|
+
# @api private
|
90
|
+
def visit_constrained(node)
|
91
|
+
definition, = node
|
92
|
+
|
93
|
+
visit(definition)
|
94
|
+
end
|
95
|
+
|
96
|
+
# @api private
|
97
|
+
def visit_enum(node)
|
98
|
+
type_node, * = node
|
99
|
+
visit(type_node)
|
100
|
+
end
|
101
|
+
|
102
|
+
# @api private
|
103
|
+
def build_class(name, parent, ns, &block)
|
104
|
+
Dry::Core::ClassBuilder
|
105
|
+
.new(name: class_name(name), parent: parent, namespace: ns)
|
106
|
+
.call(&block)
|
107
|
+
end
|
108
|
+
|
109
|
+
# @api private
|
110
|
+
def class_name(name)
|
111
|
+
inflector.classify(name)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,213 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/configurable"
|
4
|
+
require "rom/constants"
|
5
|
+
require "rom/types"
|
6
|
+
|
7
|
+
module ROM
|
8
|
+
# Component settings API
|
9
|
+
#
|
10
|
+
# @see https://dry-rb.org/gems/dry-configurable
|
11
|
+
#
|
12
|
+
# @api public
|
13
|
+
module Configurable
|
14
|
+
# @api private
|
15
|
+
def self.included(klass)
|
16
|
+
super
|
17
|
+
|
18
|
+
klass.class_eval do
|
19
|
+
include(Dry::Configurable)
|
20
|
+
|
21
|
+
class << self
|
22
|
+
prepend(Methods)
|
23
|
+
prepend(Methods::DSL)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# @api private
|
29
|
+
def self.extended(klass)
|
30
|
+
super
|
31
|
+
|
32
|
+
klass.class_eval do
|
33
|
+
extend(Dry::Configurable)
|
34
|
+
|
35
|
+
class << self
|
36
|
+
prepend(Methods)
|
37
|
+
prepend(Methods::DSL)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# @api private
|
43
|
+
module ConfigMethods
|
44
|
+
include Enumerable
|
45
|
+
|
46
|
+
# @api private
|
47
|
+
def each(&block)
|
48
|
+
values.each(&block)
|
49
|
+
end
|
50
|
+
|
51
|
+
# @api private
|
52
|
+
def inherit!(other)
|
53
|
+
update(inherit(other))
|
54
|
+
end
|
55
|
+
|
56
|
+
# @api private
|
57
|
+
def inherit(other)
|
58
|
+
hash = values.merge(other.to_h.slice(*keys)) { |key, left, right|
|
59
|
+
if _constructors[key].is_a?(Constructors::Inherit)
|
60
|
+
_constructors[key].(left, right)
|
61
|
+
else
|
62
|
+
left.nil? ? right : left
|
63
|
+
end
|
64
|
+
}
|
65
|
+
merge(hash)
|
66
|
+
end
|
67
|
+
|
68
|
+
# @api private
|
69
|
+
def join!(other, direction = :left)
|
70
|
+
update(join(other, direction))
|
71
|
+
end
|
72
|
+
|
73
|
+
# @api private
|
74
|
+
def join(other, direction = :left)
|
75
|
+
hash = values.merge(other.to_h.slice(*keys)) { |key, left, right|
|
76
|
+
if _constructors[key].is_a?(Constructors::Join)
|
77
|
+
_constructors[key].(left, right, direction)
|
78
|
+
else
|
79
|
+
direction == :left ? left : right
|
80
|
+
end
|
81
|
+
}
|
82
|
+
merge(hash)
|
83
|
+
end
|
84
|
+
|
85
|
+
# @api private
|
86
|
+
def merge(other)
|
87
|
+
dup.update(values.merge(other))
|
88
|
+
end
|
89
|
+
|
90
|
+
# @api private
|
91
|
+
def empty?
|
92
|
+
values.compact.empty?
|
93
|
+
end
|
94
|
+
|
95
|
+
# @api private
|
96
|
+
def key?(key)
|
97
|
+
_settings.key?(key)
|
98
|
+
end
|
99
|
+
|
100
|
+
# @api private
|
101
|
+
def keys
|
102
|
+
_settings.keys
|
103
|
+
end
|
104
|
+
|
105
|
+
# @api private
|
106
|
+
def fetch(...)
|
107
|
+
values.fetch(...)
|
108
|
+
end
|
109
|
+
|
110
|
+
# @api private
|
111
|
+
def to_h
|
112
|
+
values
|
113
|
+
end
|
114
|
+
alias_method :to_hash, :to_h
|
115
|
+
|
116
|
+
# @api private
|
117
|
+
def freeze
|
118
|
+
_constructors
|
119
|
+
super
|
120
|
+
end
|
121
|
+
|
122
|
+
# @api private
|
123
|
+
def _constructors
|
124
|
+
@_constructors ||= _settings.map { |setting| [setting.name, setting.constructor] }.to_h
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
module Constructors
|
129
|
+
Default = ::Struct.new(:name) do
|
130
|
+
def call(*args)
|
131
|
+
return if args.compact.empty?
|
132
|
+
|
133
|
+
block_given? ? yield(*args) : args.first
|
134
|
+
end
|
135
|
+
alias_method :[], :call
|
136
|
+
end
|
137
|
+
|
138
|
+
class Inherit < Default
|
139
|
+
def call(*args)
|
140
|
+
super { |left, right|
|
141
|
+
case left
|
142
|
+
when nil then right
|
143
|
+
when Hash then right.merge(left)
|
144
|
+
when Array then (right.map(&:dup) + left.map(&:dup)).uniq
|
145
|
+
else
|
146
|
+
left
|
147
|
+
end
|
148
|
+
}
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
class Join < Default
|
153
|
+
def call(*args)
|
154
|
+
super { |left, right, direction|
|
155
|
+
case direction
|
156
|
+
when :left then [right, left]
|
157
|
+
when :right then [left, right]
|
158
|
+
else
|
159
|
+
raise ArgumentError, "+#{direction}+ direction is not supported"
|
160
|
+
end.compact.join(".")
|
161
|
+
}
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
# @api public
|
167
|
+
module Methods
|
168
|
+
# @api public
|
169
|
+
def configure(namespace = nil, &block)
|
170
|
+
if namespace
|
171
|
+
super(&nil)
|
172
|
+
block.(config[namespace])
|
173
|
+
else
|
174
|
+
super(&block)
|
175
|
+
end
|
176
|
+
self
|
177
|
+
end
|
178
|
+
|
179
|
+
# @api private
|
180
|
+
module DSL
|
181
|
+
# @api private
|
182
|
+
def setting(name, import: nil, inherit: false, join: false, default: Undefined, **options)
|
183
|
+
if import
|
184
|
+
setting_import(name, import, **options)
|
185
|
+
elsif inherit
|
186
|
+
setting(name, default: default, constructor: Constructors::Inherit.new(name), **options)
|
187
|
+
elsif join
|
188
|
+
setting(name, default: default, constructor: Constructors::Join.new(name), **options)
|
189
|
+
else
|
190
|
+
super(name, default: default, **options)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
# @api private
|
195
|
+
def setting_import(name, setting)
|
196
|
+
# TODO: it would be great if this could just be import.with(name: name)
|
197
|
+
settings << setting.class.new(
|
198
|
+
name, input: setting.input, default: setting.default, **setting.options
|
199
|
+
)
|
200
|
+
end
|
201
|
+
|
202
|
+
# @api private
|
203
|
+
def settings
|
204
|
+
_settings
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# TODO: either extend functionality of dry-configurable or don't use it here after all
|
210
|
+
Dry::Configurable::DSL.prepend(Methods::DSL)
|
211
|
+
Dry::Configurable::Config.prepend(ConfigMethods)
|
212
|
+
end
|
213
|
+
end
|