rom 5.4.2 → 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 -71
- 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,480 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rom/header"
|
4
|
+
require "rom/mapper/model_dsl"
|
5
|
+
|
6
|
+
module ROM
|
7
|
+
class Mapper
|
8
|
+
# Mapper attribute DSL exposed by mapper subclasses
|
9
|
+
#
|
10
|
+
# This class is private even though its methods are exposed by mappers.
|
11
|
+
# Typically it's not meant to be used directly.
|
12
|
+
#
|
13
|
+
# TODO: break this madness down into smaller pieces
|
14
|
+
#
|
15
|
+
# @api private
|
16
|
+
class AttributeDSL
|
17
|
+
include ModelDSL
|
18
|
+
|
19
|
+
attr_reader :attributes, :options, :copy_keys, :symbolize_keys, :reject_keys, :steps
|
20
|
+
|
21
|
+
# @param [Array] attributes accumulator array
|
22
|
+
# @param [Hash] options
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
def initialize(attributes, options)
|
26
|
+
@attributes = attributes
|
27
|
+
@options = options
|
28
|
+
@copy_keys = options.fetch(:copy_keys)
|
29
|
+
@symbolize_keys = options.fetch(:symbolize_keys)
|
30
|
+
@prefix = options.fetch(:prefix)
|
31
|
+
@prefix_separator = options.fetch(:prefix_separator)
|
32
|
+
@reject_keys = options.fetch(:reject_keys)
|
33
|
+
@steps = []
|
34
|
+
end
|
35
|
+
|
36
|
+
# Redefine the prefix for the following attributes
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
#
|
40
|
+
# dsl = AttributeDSL.new([])
|
41
|
+
# dsl.attribute(:prefix, 'user')
|
42
|
+
#
|
43
|
+
# @api public
|
44
|
+
def prefix(value = Undefined)
|
45
|
+
if value.equal?(Undefined)
|
46
|
+
@prefix
|
47
|
+
else
|
48
|
+
@prefix = value
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Redefine the prefix separator for the following attributes
|
53
|
+
#
|
54
|
+
# @example
|
55
|
+
#
|
56
|
+
# dsl = AttributeDSL.new([])
|
57
|
+
# dsl.attribute(:prefix_separator, '.')
|
58
|
+
#
|
59
|
+
# @api public
|
60
|
+
def prefix_separator(value = Undefined)
|
61
|
+
if value.equal?(Undefined)
|
62
|
+
@prefix_separator
|
63
|
+
else
|
64
|
+
@prefix_separator = value
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Define a mapping attribute with its options and/or block
|
69
|
+
#
|
70
|
+
# @example
|
71
|
+
# dsl = AttributeDSL.new([])
|
72
|
+
#
|
73
|
+
# dsl.attribute(:name)
|
74
|
+
# dsl.attribute(:email, from: 'user_email')
|
75
|
+
# dsl.attribute(:name) { 'John' }
|
76
|
+
# dsl.attribute(:name) { |t| t.upcase }
|
77
|
+
#
|
78
|
+
# @api public
|
79
|
+
def attribute(name, options = EMPTY_HASH, &block)
|
80
|
+
with_attr_options(name, options) do |attr_options|
|
81
|
+
if options[:type] && block
|
82
|
+
raise ArgumentError,
|
83
|
+
"can't specify type and block at the same time"
|
84
|
+
end
|
85
|
+
attr_options[:coercer] = block if block
|
86
|
+
add_attribute(name, attr_options)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def exclude(name)
|
91
|
+
attributes << [name, {exclude: true}]
|
92
|
+
end
|
93
|
+
|
94
|
+
# Perform transformations sequentially
|
95
|
+
#
|
96
|
+
# @example
|
97
|
+
# dsl = AttributeDSL.new()
|
98
|
+
#
|
99
|
+
# dsl.step do
|
100
|
+
# attribute :name
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# @api public
|
104
|
+
def step(options = EMPTY_HASH, &block)
|
105
|
+
steps << new(options, &block)
|
106
|
+
end
|
107
|
+
|
108
|
+
# Define an embedded attribute
|
109
|
+
#
|
110
|
+
# Block exposes the attribute dsl too
|
111
|
+
#
|
112
|
+
# @example
|
113
|
+
# dsl = AttributeDSL.new([])
|
114
|
+
#
|
115
|
+
# dsl.embedded :tags, type: :array do
|
116
|
+
# attribute :name
|
117
|
+
# end
|
118
|
+
#
|
119
|
+
# dsl.embedded :address, type: :hash do
|
120
|
+
# model Address
|
121
|
+
# attribute :name
|
122
|
+
# end
|
123
|
+
#
|
124
|
+
# @param [Symbol] name attribute
|
125
|
+
#
|
126
|
+
# @param [Hash] options
|
127
|
+
# @option options [Symbol] :type Embedded type can be :hash or :array
|
128
|
+
# @option options [Symbol] :prefix Prefix that should be used for
|
129
|
+
# its attributes
|
130
|
+
#
|
131
|
+
# @api public
|
132
|
+
def embedded(name, options, &block)
|
133
|
+
with_attr_options(name) do |attr_options|
|
134
|
+
mapper = options[:mapper]
|
135
|
+
|
136
|
+
if mapper
|
137
|
+
embedded_options = {type: :array}.update(options)
|
138
|
+
attributes_from_mapper(
|
139
|
+
mapper, name, embedded_options.update(attr_options)
|
140
|
+
)
|
141
|
+
else
|
142
|
+
dsl = new(options, &block)
|
143
|
+
attr_options.update(options)
|
144
|
+
add_attribute(
|
145
|
+
name, {header: dsl.header, type: :array}.update(attr_options)
|
146
|
+
)
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
151
|
+
# Define an embedded hash attribute that requires "wrapping" transformation
|
152
|
+
#
|
153
|
+
# Typically this is used in sql context when relation is a join.
|
154
|
+
#
|
155
|
+
# @example
|
156
|
+
# dsl = AttributeDSL.new([])
|
157
|
+
#
|
158
|
+
# dsl.wrap(address: [:street, :zipcode, :city])
|
159
|
+
#
|
160
|
+
# dsl.wrap(:address) do
|
161
|
+
# model Address
|
162
|
+
# attribute :street
|
163
|
+
# attribute :zipcode
|
164
|
+
# attribute :city
|
165
|
+
# end
|
166
|
+
#
|
167
|
+
# @see AttributeDSL#embedded
|
168
|
+
#
|
169
|
+
# @api public
|
170
|
+
def wrap(*args, &block)
|
171
|
+
ensure_mapper_configuration("wrap", args, block_given?)
|
172
|
+
|
173
|
+
with_name_or_options(*args) do |name, options, mapper|
|
174
|
+
wrap_options = {type: :hash, wrap: true}.update(options)
|
175
|
+
|
176
|
+
if mapper
|
177
|
+
attributes_from_mapper(mapper, name, wrap_options)
|
178
|
+
else
|
179
|
+
dsl(name, wrap_options, &block)
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# Define an embedded hash attribute that requires "unwrapping" transformation
|
185
|
+
#
|
186
|
+
# Typically this is used in no-sql context to normalize data before
|
187
|
+
# inserting to sql gateway.
|
188
|
+
#
|
189
|
+
# @example
|
190
|
+
# dsl = AttributeDSL.new([])
|
191
|
+
#
|
192
|
+
# dsl.unwrap(address: [:street, :zipcode, :city])
|
193
|
+
#
|
194
|
+
# dsl.unwrap(:address) do
|
195
|
+
# attribute :street
|
196
|
+
# attribute :zipcode
|
197
|
+
# attribute :city
|
198
|
+
# end
|
199
|
+
#
|
200
|
+
# @see AttributeDSL#embedded
|
201
|
+
#
|
202
|
+
# @api public
|
203
|
+
def unwrap(*args, &block)
|
204
|
+
with_name_or_options(*args) do |name, options, mapper|
|
205
|
+
unwrap_options = {type: :hash, unwrap: true}.update(options)
|
206
|
+
|
207
|
+
if mapper
|
208
|
+
attributes_from_mapper(mapper, name, unwrap_options)
|
209
|
+
else
|
210
|
+
dsl(name, unwrap_options, &block)
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Define an embedded hash attribute that requires "grouping" transformation
|
216
|
+
#
|
217
|
+
# Typically this is used in sql context when relation is a join.
|
218
|
+
#
|
219
|
+
# @example
|
220
|
+
# dsl = AttributeDSL.new([])
|
221
|
+
#
|
222
|
+
# dsl.group(tags: [:name])
|
223
|
+
#
|
224
|
+
# dsl.group(:tags) do
|
225
|
+
# model Tag
|
226
|
+
# attribute :name
|
227
|
+
# end
|
228
|
+
#
|
229
|
+
# @see AttributeDSL#embedded
|
230
|
+
#
|
231
|
+
# @api public
|
232
|
+
def group(*args, &block)
|
233
|
+
ensure_mapper_configuration("group", args, block_given?)
|
234
|
+
|
235
|
+
with_name_or_options(*args) do |name, options, mapper|
|
236
|
+
group_options = {type: :array, group: true}.update(options)
|
237
|
+
|
238
|
+
if mapper
|
239
|
+
attributes_from_mapper(mapper, name, group_options)
|
240
|
+
else
|
241
|
+
dsl(name, group_options, &block)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
# Define an embedded array attribute that requires "ungrouping" transformation
|
247
|
+
#
|
248
|
+
# Typically this is used in non-sql context being prepared for import to sql.
|
249
|
+
#
|
250
|
+
# @example
|
251
|
+
# dsl = AttributeDSL.new([])
|
252
|
+
# dsl.ungroup(tags: [:name])
|
253
|
+
#
|
254
|
+
# @see AttributeDSL#embedded
|
255
|
+
#
|
256
|
+
# @api public
|
257
|
+
def ungroup(*args, &block)
|
258
|
+
with_name_or_options(*args) do |name, options, *|
|
259
|
+
ungroup_options = {type: :array, ungroup: true}.update(options)
|
260
|
+
dsl(name, ungroup_options, &block)
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Define an embedded hash attribute that requires "fold" transformation
|
265
|
+
#
|
266
|
+
# Typically this is used in sql context to fold single joined field
|
267
|
+
# to the array of values.
|
268
|
+
#
|
269
|
+
# @example
|
270
|
+
# dsl = AttributeDSL.new([])
|
271
|
+
#
|
272
|
+
# dsl.fold(tags: [:name])
|
273
|
+
#
|
274
|
+
# @see AttributeDSL#embedded
|
275
|
+
#
|
276
|
+
# @api public
|
277
|
+
def fold(*args, &block)
|
278
|
+
with_name_or_options(*args) do |name, *|
|
279
|
+
fold_options = {type: :array, fold: true}
|
280
|
+
dsl(name, fold_options, &block)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# Define an embedded hash attribute that requires "unfold" transformation
|
285
|
+
#
|
286
|
+
# Typically this is used in non-sql context to convert array of
|
287
|
+
# values (like in Cassandra 'SET' or 'LIST' types) to array of tuples.
|
288
|
+
#
|
289
|
+
# Source values are assigned to the first key, the other keys being left blank.
|
290
|
+
#
|
291
|
+
# @example
|
292
|
+
# dsl = AttributeDSL.new([])
|
293
|
+
#
|
294
|
+
# dsl.unfold(tags: [:name, :type], from: :tags_list)
|
295
|
+
#
|
296
|
+
# dsl.unfold :tags, from: :tags_list do
|
297
|
+
# attribute :name, from: :tag_name
|
298
|
+
# attribute :type, from: :tag_type
|
299
|
+
# end
|
300
|
+
#
|
301
|
+
# @see AttributeDSL#embedded
|
302
|
+
#
|
303
|
+
# @api public
|
304
|
+
def unfold(name, options = EMPTY_HASH)
|
305
|
+
with_attr_options(name, options) do |attr_options|
|
306
|
+
old_name = attr_options.fetch(:from, name)
|
307
|
+
dsl(old_name, type: :array, unfold: true) do
|
308
|
+
attribute name, attr_options
|
309
|
+
yield if block_given?
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
# Define an embedded combined attribute that requires "combine" transformation
|
315
|
+
#
|
316
|
+
# Typically this can be used to process results of eager-loading
|
317
|
+
#
|
318
|
+
# @example
|
319
|
+
# dsl = AttributeDSL.new([])
|
320
|
+
#
|
321
|
+
# dsl.combine(:tags, user_id: :id) do
|
322
|
+
# model Tag
|
323
|
+
#
|
324
|
+
# attribute :name
|
325
|
+
# end
|
326
|
+
#
|
327
|
+
# @param [Symbol] name
|
328
|
+
# @param [Hash] options
|
329
|
+
# @option options [Hash] :on The "join keys"
|
330
|
+
# @option options [Symbol] :type The type, either :array (default) or :hash
|
331
|
+
#
|
332
|
+
# @api public
|
333
|
+
def combine(name, options, &block)
|
334
|
+
dsl = new(options, &block)
|
335
|
+
|
336
|
+
attr_opts = {
|
337
|
+
type: options.fetch(:type, :array),
|
338
|
+
keys: options.fetch(:on),
|
339
|
+
combine: true,
|
340
|
+
header: dsl.header
|
341
|
+
}
|
342
|
+
|
343
|
+
add_attribute(name, attr_opts)
|
344
|
+
end
|
345
|
+
|
346
|
+
# Generate a header from attribute definitions
|
347
|
+
#
|
348
|
+
# @return [Header]
|
349
|
+
#
|
350
|
+
# @api private
|
351
|
+
def header
|
352
|
+
Header.coerce(attributes, copy_keys: copy_keys, model: model, reject_keys: reject_keys)
|
353
|
+
end
|
354
|
+
|
355
|
+
private
|
356
|
+
|
357
|
+
# Remove the attribute used somewhere else (in wrap, group, model etc.)
|
358
|
+
#
|
359
|
+
# @api private
|
360
|
+
def remove(*names)
|
361
|
+
attributes.delete_if { |attr| names.include?(attr.first) }
|
362
|
+
end
|
363
|
+
|
364
|
+
# Handle attribute options common for all definitions
|
365
|
+
#
|
366
|
+
# @api private
|
367
|
+
def with_attr_options(name, options = EMPTY_HASH)
|
368
|
+
attr_options = options.dup
|
369
|
+
|
370
|
+
if @prefix
|
371
|
+
attr_options[:from] ||= "#{@prefix}#{@prefix_separator}#{name}"
|
372
|
+
attr_options[:from] = attr_options[:from].to_sym if name.is_a? Symbol
|
373
|
+
end
|
374
|
+
|
375
|
+
attr_options.update(from: attr_options.fetch(:from) { name }.to_s) if symbolize_keys
|
376
|
+
|
377
|
+
yield(attr_options)
|
378
|
+
end
|
379
|
+
|
380
|
+
# Handle "name or options" syntax used by `wrap` and `group`
|
381
|
+
#
|
382
|
+
# @api private
|
383
|
+
def with_name_or_options(*args)
|
384
|
+
name, options =
|
385
|
+
if args.size > 1
|
386
|
+
args
|
387
|
+
else
|
388
|
+
[args.first, {}]
|
389
|
+
end
|
390
|
+
|
391
|
+
yield(name, options, options[:mapper])
|
392
|
+
end
|
393
|
+
|
394
|
+
# Create another instance of the dsl for nested definitions
|
395
|
+
#
|
396
|
+
# This is used by embedded, wrap and group
|
397
|
+
#
|
398
|
+
# @api private
|
399
|
+
def dsl(name_or_attrs, options, &block)
|
400
|
+
if block
|
401
|
+
attributes_from_block(name_or_attrs, options, &block)
|
402
|
+
else
|
403
|
+
attributes_from_hash(name_or_attrs, options)
|
404
|
+
end
|
405
|
+
end
|
406
|
+
|
407
|
+
# Define attributes from a nested block
|
408
|
+
#
|
409
|
+
# Used by embedded, wrap and group
|
410
|
+
#
|
411
|
+
# @api private
|
412
|
+
def attributes_from_block(name, options, &block)
|
413
|
+
dsl = new(options, &block)
|
414
|
+
header = dsl.header
|
415
|
+
add_attribute(name, options.update(header: header))
|
416
|
+
header.each { |attr| remove(attr.key) unless name == attr.key }
|
417
|
+
end
|
418
|
+
|
419
|
+
# Define attributes from the `name => attributes` hash syntax
|
420
|
+
#
|
421
|
+
# Used by wrap and group
|
422
|
+
#
|
423
|
+
# @api private
|
424
|
+
def attributes_from_hash(hash, options)
|
425
|
+
hash.each do |name, header|
|
426
|
+
with_attr_options(name, options) do |attr_options|
|
427
|
+
add_attribute(name, attr_options.update(header: header.zip))
|
428
|
+
header.each { |attr| remove(attr) unless name == attr }
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
# Infer mapper header for an embedded attribute
|
434
|
+
#
|
435
|
+
# @api private
|
436
|
+
def attributes_from_mapper(mapper, name, options)
|
437
|
+
if mapper.is_a?(Class)
|
438
|
+
add_attribute(name, {header: mapper.header}.update(options))
|
439
|
+
else
|
440
|
+
raise(
|
441
|
+
ArgumentError, ":mapper must be a class #{mapper.inspect}"
|
442
|
+
)
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
# Add a new attribute and make sure it overrides previous definition
|
447
|
+
#
|
448
|
+
# @api private
|
449
|
+
def add_attribute(name, options)
|
450
|
+
remove(name, name.to_s)
|
451
|
+
attributes << [name, options]
|
452
|
+
end
|
453
|
+
|
454
|
+
# Create a new dsl instance of potentially overidden options
|
455
|
+
#
|
456
|
+
# Embedded, wrap and group can override top-level options like `prefix`
|
457
|
+
#
|
458
|
+
# @api private
|
459
|
+
def new(options, &block)
|
460
|
+
dsl = self.class.new([], @options.merge(options))
|
461
|
+
dsl.instance_exec(&block) unless block.nil?
|
462
|
+
dsl
|
463
|
+
end
|
464
|
+
|
465
|
+
# Ensure the mapping configuration isn't ambiguous
|
466
|
+
#
|
467
|
+
# @api private
|
468
|
+
def ensure_mapper_configuration(method_name, args, block_present)
|
469
|
+
if args.first.is_a?(Hash) && block_present
|
470
|
+
raise MapperMisconfiguredError,
|
471
|
+
"Cannot configure `#{method_name}#` using both options and a block"
|
472
|
+
end
|
473
|
+
if args.first.is_a?(Hash) && args.first[:mapper]
|
474
|
+
raise MapperMisconfiguredError,
|
475
|
+
"Cannot configure `#{method_name}#` using both options and a mapper"
|
476
|
+
end
|
477
|
+
end
|
478
|
+
end
|
479
|
+
end
|
480
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "dry/core/class_attributes"
|
4
|
+
require "rom/mapper/attribute_dsl"
|
5
|
+
|
6
|
+
module ROM
|
7
|
+
class Mapper
|
8
|
+
# Mapper class-level DSL including Attribute DSL and Model DSL
|
9
|
+
module DSL
|
10
|
+
# Extend mapper class with macros and DSL methods
|
11
|
+
#
|
12
|
+
# @api private
|
13
|
+
def self.included(klass)
|
14
|
+
klass.extend(Dry::Core::ClassAttributes)
|
15
|
+
klass.extend(ClassMethods)
|
16
|
+
end
|
17
|
+
|
18
|
+
# Class methods for all mappers
|
19
|
+
#
|
20
|
+
# @private
|
21
|
+
module ClassMethods
|
22
|
+
# Set base ivars for the mapper class
|
23
|
+
#
|
24
|
+
# @api private
|
25
|
+
def inherited(klass)
|
26
|
+
super
|
27
|
+
|
28
|
+
klass.instance_variable_set("@attributes", nil)
|
29
|
+
klass.instance_variable_set("@header", nil)
|
30
|
+
klass.instance_variable_set("@attribute_dsl", nil)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return base_relation used for creating mapper registry
|
34
|
+
#
|
35
|
+
# This is used to "gather" mappers under same root name
|
36
|
+
#
|
37
|
+
# @api private
|
38
|
+
def base_relation
|
39
|
+
superclass.relation || relation
|
40
|
+
end
|
41
|
+
|
42
|
+
# Return header of the mapper
|
43
|
+
#
|
44
|
+
# This is memoized so mutating mapper class won't have an effect wrt
|
45
|
+
# header after it was initialized for the first time.
|
46
|
+
#
|
47
|
+
# TODO: freezing mapper class here is probably a good idea
|
48
|
+
#
|
49
|
+
# @api private
|
50
|
+
def header
|
51
|
+
@header ||= attribute_dsl.header
|
52
|
+
end
|
53
|
+
|
54
|
+
# @api private
|
55
|
+
def respond_to_missing?(name, _include_private = false)
|
56
|
+
attribute_dsl.respond_to?(name) || super
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
# Return default Attribute DSL options based on settings of the mapper
|
62
|
+
# class
|
63
|
+
#
|
64
|
+
# @api private
|
65
|
+
def options
|
66
|
+
{copy_keys: copy_keys,
|
67
|
+
prefix: prefix,
|
68
|
+
prefix_separator: prefix_separator,
|
69
|
+
symbolize_keys: symbolize_keys,
|
70
|
+
reject_keys: reject_keys}
|
71
|
+
end
|
72
|
+
|
73
|
+
# Return default attributes that might have been inherited from the
|
74
|
+
# superclass
|
75
|
+
#
|
76
|
+
# @api private
|
77
|
+
def attributes
|
78
|
+
@attributes ||=
|
79
|
+
if superclass.respond_to?(:attributes, true) && config.inherit_header
|
80
|
+
superclass.__send__(:attributes).dup
|
81
|
+
else
|
82
|
+
[]
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
# Create the attribute DSL instance used by the mapper class
|
87
|
+
#
|
88
|
+
# @api private
|
89
|
+
def attribute_dsl
|
90
|
+
@attribute_dsl ||= AttributeDSL.new(attributes, config.to_h)
|
91
|
+
end
|
92
|
+
|
93
|
+
# Delegate Attribute DSL method to the dsl instance
|
94
|
+
#
|
95
|
+
# @api private
|
96
|
+
def method_missing(name, *args, &block)
|
97
|
+
if attribute_dsl.respond_to?(name)
|
98
|
+
attribute_dsl.public_send(name, *args, &block)
|
99
|
+
else
|
100
|
+
super
|
101
|
+
end
|
102
|
+
end
|
103
|
+
ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rom/model_builder"
|
4
|
+
|
5
|
+
module ROM
|
6
|
+
class Mapper
|
7
|
+
# Model DSL allows setting a model class
|
8
|
+
#
|
9
|
+
# @private
|
10
|
+
module ModelDSL
|
11
|
+
attr_reader :attributes, :builder, :klass
|
12
|
+
|
13
|
+
DEFAULT_TYPE = :poro
|
14
|
+
|
15
|
+
# Set or generate a model
|
16
|
+
#
|
17
|
+
# @example
|
18
|
+
# class MyDefinition
|
19
|
+
# include ROM::Mapper::ModelDSL
|
20
|
+
#
|
21
|
+
# def initialize
|
22
|
+
# @attributes = [[:name], [:title]]
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# definition = MyDefinition.new
|
27
|
+
#
|
28
|
+
# # just set a model constant
|
29
|
+
# definition.model(User)
|
30
|
+
#
|
31
|
+
# # generate model class for the attributes
|
32
|
+
# definition.model(name: 'User')
|
33
|
+
#
|
34
|
+
# @api public
|
35
|
+
def model(options = nil)
|
36
|
+
if options.is_a?(Class)
|
37
|
+
@klass = options
|
38
|
+
elsif options
|
39
|
+
type = options.fetch(:type) { DEFAULT_TYPE }
|
40
|
+
@builder = ModelBuilder[type].new(options)
|
41
|
+
end
|
42
|
+
|
43
|
+
build_class unless options
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
# Build a model class using a specialized builder
|
49
|
+
#
|
50
|
+
# @api private
|
51
|
+
def build_class
|
52
|
+
return klass if klass
|
53
|
+
|
54
|
+
included_attrs = attributes.reject do |_name, opts|
|
55
|
+
opts && opts[:exclude]
|
56
|
+
end
|
57
|
+
builder&.call(included_attrs.map(&:first))
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|