rom 5.3.2 → 6.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -47
  3. data/LICENSE +1 -1
  4. data/README.md +6 -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 +273 -36
@@ -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