rom 0.8.1 → 0.9.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +4 -0
  3. data/README.md +5 -1
  4. data/lib/rom.rb +35 -16
  5. data/lib/rom/command.rb +1 -9
  6. data/lib/rom/commands/graph/class_interface.rb +2 -2
  7. data/lib/rom/constants.rb +0 -6
  8. data/lib/rom/{env.rb → container.rb} +3 -3
  9. data/lib/rom/environment.rb +238 -0
  10. data/lib/rom/environment_plugin.rb +17 -0
  11. data/lib/rom/environment_plugins/auto_registration.rb +17 -0
  12. data/lib/rom/global.rb +0 -203
  13. data/lib/rom/mapper_registry.rb +2 -0
  14. data/lib/rom/pipeline.rb +2 -0
  15. data/lib/rom/plugin.rb +4 -18
  16. data/lib/rom/plugin_base.rb +31 -0
  17. data/lib/rom/plugin_registry.rb +54 -17
  18. data/lib/rom/relation.rb +54 -11
  19. data/lib/rom/relation/class_interface.rb +14 -21
  20. data/lib/rom/relation/curried.rb +36 -2
  21. data/lib/rom/relation/graph.rb +7 -0
  22. data/lib/rom/relation_registry.rb +4 -0
  23. data/lib/rom/setup.rb +9 -8
  24. data/lib/rom/setup/finalize.rb +5 -5
  25. data/lib/rom/version.rb +1 -1
  26. data/rom.gemspec +2 -0
  27. data/spec/integration/commands/create_spec.rb +1 -1
  28. data/spec/integration/commands/update_spec.rb +1 -1
  29. data/spec/integration/mappers/unwrap_spec.rb +1 -1
  30. data/spec/spec_helper.rb +2 -0
  31. data/spec/unit/rom/{env_spec.rb → container_spec.rb} +5 -5
  32. data/spec/unit/rom/plugin_spec.rb +0 -8
  33. data/spec/unit/rom/relation/composite_spec.rb +2 -2
  34. data/spec/unit/rom/relation/curried_spec.rb +53 -0
  35. data/spec/unit/rom/relation/graph_spec.rb +4 -0
  36. data/spec/unit/rom/relation/lazy/combine_spec.rb +6 -6
  37. data/spec/unit/rom/relation/lazy_spec.rb +4 -8
  38. data/spec/unit/rom/relation_spec.rb +0 -14
  39. data/spec/unit/rom/setup_spec.rb +1 -1
  40. metadata +52 -35
  41. data/lib/rom/header.rb +0 -193
  42. data/lib/rom/header/attribute.rb +0 -184
  43. data/lib/rom/mapper.rb +0 -103
  44. data/lib/rom/mapper/attribute_dsl.rb +0 -477
  45. data/lib/rom/mapper/dsl.rb +0 -119
  46. data/lib/rom/mapper/model_dsl.rb +0 -55
  47. data/lib/rom/model_builder.rb +0 -101
  48. data/lib/rom/processor.rb +0 -28
  49. data/lib/rom/processor/transproc.rb +0 -388
  50. data/lib/rom/relation/lazy.rb +0 -145
  51. data/lib/rom/support/array_dataset.rb +0 -41
  52. data/lib/rom/support/class_builder.rb +0 -44
  53. data/lib/rom/support/class_macros.rb +0 -56
  54. data/lib/rom/support/data_proxy.rb +0 -102
  55. data/lib/rom/support/deprecations.rb +0 -36
  56. data/lib/rom/support/enumerable_dataset.rb +0 -65
  57. data/lib/rom/support/inflector.rb +0 -73
  58. data/lib/rom/support/options.rb +0 -195
  59. data/lib/rom/support/registry.rb +0 -43
  60. data/spec/unit/rom/header_spec.rb +0 -102
  61. data/spec/unit/rom/mapper/dsl_spec.rb +0 -467
  62. data/spec/unit/rom/mapper_spec.rb +0 -84
  63. data/spec/unit/rom/model_builder_spec.rb +0 -46
  64. data/spec/unit/rom/processor/transproc_spec.rb +0 -448
  65. data/spec/unit/rom/support/array_dataset_spec.rb +0 -61
  66. data/spec/unit/rom/support/class_builder_spec.rb +0 -42
  67. data/spec/unit/rom/support/enumerable_dataset_spec.rb +0 -17
  68. data/spec/unit/rom/support/inflector_spec.rb +0 -89
  69. data/spec/unit/rom/support/options_spec.rb +0 -119
@@ -1,184 +0,0 @@
1
- module ROM
2
- class Header
3
- # An attribute provides information about a specific attribute in a tuple
4
- #
5
- # This may include information about how an attribute should be renamed,
6
- # or how its value should coerced.
7
- #
8
- # More complex attributes describe how an attribute should be transformed.
9
- #
10
- # @private
11
- class Attribute
12
- include Equalizer.new(:name, :key, :type)
13
-
14
- # @return [Symbol] name of an attribute
15
- #
16
- # @api private
17
- attr_reader :name
18
-
19
- # @return [Symbol] key of an attribute that corresponds to tuple attribute
20
- #
21
- # @api private
22
- attr_reader :key
23
-
24
- # @return [Symbol] type identifier (defaults to :object)
25
- #
26
- # @api private
27
- attr_reader :type
28
-
29
- # @return [Hash] additional meta information
30
- #
31
- # @api private
32
- attr_reader :meta
33
-
34
- # Return attribute class for a given meta hash
35
- #
36
- # @param [Hash] meta hash with type information and optional transformation info
37
- #
38
- # @return [Class]
39
- #
40
- # @api private
41
- def self.[](meta)
42
- key = (meta.keys & TYPE_MAP.keys).first
43
- TYPE_MAP.fetch(key || meta[:type], self)
44
- end
45
-
46
- # Coerce an array with attribute meta-data into an attribute object
47
- #
48
- # @param [Array<Symbol,Hash>] input attribute name/options pair
49
- #
50
- # @return [Attribute]
51
- #
52
- # @api private
53
- def self.coerce(input)
54
- name = input[0]
55
- meta = (input[1] || {}).dup
56
-
57
- meta[:type] ||= :object
58
-
59
- if meta.key?(:header)
60
- meta[:header] = Header.coerce(meta[:header], model: meta[:model])
61
- end
62
-
63
- self[meta].new(name, meta)
64
- end
65
-
66
- # @api private
67
- def initialize(name, meta)
68
- @name = name
69
- @meta = meta
70
- @key = meta.fetch(:from) { name }
71
- @type = meta.fetch(:type)
72
- end
73
-
74
- # Return if an attribute has a specific type identifier
75
- #
76
- # @api private
77
- def typed?
78
- type != :object
79
- end
80
-
81
- # Return if an attribute should be aliased
82
- #
83
- # @api private
84
- def aliased?
85
- key != name
86
- end
87
-
88
- # Return :key-to-:name mapping hash
89
- #
90
- # @return [Hash]
91
- #
92
- # @api private
93
- def mapping
94
- { key => name }
95
- end
96
- end
97
-
98
- # Embedded attribute is a special attribute type that has a header
99
- #
100
- # This is the base of complex attributes like Hash or Group
101
- #
102
- # @private
103
- class Embedded < Attribute
104
- include Equalizer.new(:name, :key, :type, :header)
105
-
106
- # return [Header] header of an attribute
107
- #
108
- # @api private
109
- attr_reader :header
110
-
111
- # @api private
112
- def initialize(*)
113
- super
114
- @header = meta.fetch(:header)
115
- end
116
-
117
- # Return tuple keys from the header
118
- #
119
- # @return [Array<Symbol>]
120
- #
121
- # @api private
122
- def tuple_keys
123
- header.tuple_keys
124
- end
125
-
126
- def pop_keys
127
- header.pop_keys
128
- end
129
- end
130
-
131
- # Array is an embedded attribute type
132
- Array = Class.new(Embedded)
133
-
134
- # Hash is an embedded attribute type
135
- Hash = Class.new(Embedded)
136
-
137
- # Combined is an embedded attribute type describing combination of multiple
138
- # relations
139
- Combined = Class.new(Embedded)
140
-
141
- # Wrap is a special type of Hash attribute that requires wrapping
142
- # transformation
143
- Wrap = Class.new(Hash)
144
-
145
- # Unwrap is a special type of Hash attribute that requires unwrapping
146
- # transformation
147
- Unwrap = Class.new(Hash)
148
-
149
- # Group is a special type of Array attribute that requires grouping
150
- # transformation
151
- Group = Class.new(Array)
152
-
153
- # Ungroup is a special type of Array attribute that requires ungrouping
154
- # transformation
155
- Ungroup = Class.new(Array)
156
-
157
- # Fold is a special type of Array attribute that requires folding
158
- # transformation
159
- Fold = Class.new(Array)
160
-
161
- # Unfold is a special type of Array attribute that requires unfolding
162
- # transformation
163
- Unfold = Class.new(Array)
164
-
165
- # Exclude is a special type of Attribute to be removed
166
- Exclude = Class.new(Attribute)
167
-
168
- # TYPE_MAP is a (hash) map of ROM::Header identifiers to ROM::Header types
169
- #
170
- # @private
171
- TYPE_MAP = {
172
- combine: Combined,
173
- wrap: Wrap,
174
- unwrap: Unwrap,
175
- group: Group,
176
- ungroup: Ungroup,
177
- fold: Fold,
178
- unfold: Unfold,
179
- hash: Hash,
180
- array: Array,
181
- exclude: Exclude
182
- }
183
- end
184
- end
data/lib/rom/mapper.rb DELETED
@@ -1,103 +0,0 @@
1
- require 'rom/mapper/dsl'
2
-
3
- module ROM
4
- # Mapper is a simple object that uses transformers to load relations
5
- #
6
- # @private
7
- class Mapper
8
- include DSL
9
- include Equalizer.new(:transformers, :header)
10
-
11
- defines :relation, :register_as, :symbolize_keys,
12
- :prefix, :prefix_separator, :inherit_header, :reject_keys
13
-
14
- inherit_header true
15
- reject_keys false
16
- prefix_separator '_'.freeze
17
-
18
- # @return [Object] transformers object built by a processor
19
- #
20
- # @api private
21
- attr_reader :transformers
22
-
23
- # @return [Header] header that was used to build the transformers
24
- #
25
- # @api private
26
- attr_reader :header
27
-
28
- # Register suclasses during setup phase
29
- #
30
- # @api private
31
- def self.inherited(klass)
32
- super
33
- ROM.register_mapper(klass)
34
- end
35
-
36
- # @return [Hash] registered processors
37
- #
38
- # @api private
39
- def self.processors
40
- @_processors ||= {}
41
- end
42
-
43
- # Register a processor class
44
- #
45
- # @return [Hash]
46
- #
47
- # @api private
48
- def self.register_processor(processor)
49
- name = processor.name.split('::').last.downcase.to_sym
50
- processors.update(name => processor)
51
- end
52
-
53
- # Prepares an array of headers for a potentially multistep mapper
54
- #
55
- # @return [Array<Header>]
56
- #
57
- # @api private
58
- def self.headers(header)
59
- return [header] if steps.empty?
60
- return steps.map(&:header) if attributes.empty?
61
- raise(MapperMisconfiguredError, "cannot mix outer attributes and steps")
62
- end
63
-
64
- # Build a mapper using provided processor type
65
- #
66
- # @return [Mapper]
67
- #
68
- # @api private
69
- def self.build(header = self.header, processor = :transproc)
70
- processor = Mapper.processors.fetch(processor)
71
- transformers = headers(header).map(&processor.method(:build))
72
- new(transformers, header)
73
- end
74
-
75
- # @api private
76
- def self.registry(descendants)
77
- descendants.each_with_object({}) do |klass, h|
78
- name = klass.register_as || klass.relation
79
- (h[klass.base_relation] ||= {})[name] = klass.build
80
- end
81
- end
82
-
83
- # @api private
84
- def initialize(transformers, header)
85
- @transformers = Array(transformers)
86
- @header = header
87
- end
88
-
89
- # @return [Class] optional model that is instantiated by a mapper
90
- #
91
- # @api private
92
- def model
93
- header.model
94
- end
95
-
96
- # Process a relation using the transformers
97
- #
98
- # @api private
99
- def call(relation)
100
- transformers.reduce(relation.to_a) { |a, e| e.call(a) }
101
- end
102
- end
103
- end
@@ -1,477 +0,0 @@
1
- require 'rom/header'
2
- require 'rom/mapper/model_dsl'
3
-
4
- module ROM
5
- class Mapper
6
- # Mapper attribute DSL exposed by mapper subclasses
7
- #
8
- # This class is private even though its methods are exposed by mappers.
9
- # Typically it's not meant to be used directly.
10
- #
11
- # TODO: break this madness down into smaller pieces
12
- #
13
- # @api private
14
- class AttributeDSL
15
- include ModelDSL
16
-
17
- attr_reader :attributes, :options, :symbolize_keys, :reject_keys, :steps
18
-
19
- # @param [Array] attributes accumulator array
20
- # @param [Hash] options
21
- #
22
- # @api private
23
- def initialize(attributes, options)
24
- @attributes = attributes
25
- @options = options
26
- @symbolize_keys = options.fetch(:symbolize_keys)
27
- @prefix = options.fetch(:prefix)
28
- @prefix_separator = options.fetch(:prefix_separator)
29
- @reject_keys = options.fetch(:reject_keys)
30
- @steps = []
31
- end
32
-
33
- # Redefine the prefix for the following attributes
34
- #
35
- # @example
36
- #
37
- # dsl = AttributeDSL.new([])
38
- # dsl.attribute(:prefix, 'user')
39
- #
40
- # @api public
41
- def prefix(value = Undefined)
42
- if value.equal?(Undefined)
43
- @prefix
44
- else
45
- @prefix = value
46
- end
47
- end
48
-
49
- # Redefine the prefix separator for the following attributes
50
- #
51
- # @example
52
- #
53
- # dsl = AttributeDSL.new([])
54
- # dsl.attribute(:prefix_separator, '.')
55
- #
56
- # @api public
57
- def prefix_separator(value = Undefined)
58
- if value.equal?(Undefined)
59
- @prefix_separator
60
- else
61
- @prefix_separator = value
62
- end
63
- end
64
-
65
- # Define a mapping attribute with its options and/or block
66
- #
67
- # @example
68
- # dsl = AttributeDSL.new([])
69
- #
70
- # dsl.attribute(:name)
71
- # dsl.attribute(:email, from: 'user_email')
72
- # dsl.attribute(:name) { 'John' }
73
- # dsl.attribute(:name) { |t| t.upcase }
74
- #
75
- # @api public
76
- def attribute(name, options = EMPTY_HASH, &block)
77
- with_attr_options(name, options) do |attr_options|
78
- raise ArgumentError,
79
- "can't specify type and block at the same time" if options[:type] && block
80
- attr_options[:coercer] = block if block
81
- add_attribute(name, attr_options)
82
- end
83
- end
84
-
85
- def exclude(name)
86
- attributes << [name, { exclude: true }]
87
- end
88
-
89
- # Perform transformations sequentially
90
- #
91
- # @example
92
- # dsl = AttributeDSL.new()
93
- #
94
- # dsl.step do
95
- # attribute :name
96
- # end
97
- #
98
- # @api public
99
- def step(options = EMPTY_HASH, &block)
100
- steps << new(options, &block)
101
- end
102
-
103
- # Define an embedded attribute
104
- #
105
- # Block exposes the attribute dsl too
106
- #
107
- # @example
108
- # dsl = AttributeDSL.new([])
109
- #
110
- # dsl.embedded :tags, type: :array do
111
- # attribute :name
112
- # end
113
- #
114
- # dsl.embedded :address, type: :hash do
115
- # model Address
116
- # attribute :name
117
- # end
118
- #
119
- # @param [Symbol] name attribute
120
- #
121
- # @param [Hash] options
122
- # @option options [Symbol] :type Embedded type can be :hash or :array
123
- # @option options [Symbol] :prefix Prefix that should be used for
124
- # its attributes
125
- #
126
- # @api public
127
- def embedded(name, options, &block)
128
- with_attr_options(name) do |attr_options|
129
- mapper = options[:mapper]
130
-
131
- if mapper
132
- embedded_options = { type: :array }.update(options)
133
- attributes_from_mapper(
134
- mapper, name, embedded_options.update(attr_options)
135
- )
136
- else
137
- dsl = new(options, &block)
138
- attr_options.update(options)
139
- add_attribute(
140
- name, { header: dsl.header, type: :array }.update(attr_options)
141
- )
142
- end
143
- end
144
- end
145
-
146
- # Define an embedded hash attribute that requires "wrapping" transformation
147
- #
148
- # Typically this is used in sql context when relation is a join.
149
- #
150
- # @example
151
- # dsl = AttributeDSL.new([])
152
- #
153
- # dsl.wrap(address: [:street, :zipcode, :city])
154
- #
155
- # dsl.wrap(:address) do
156
- # model Address
157
- # attribute :street
158
- # attribute :zipcode
159
- # attribute :city
160
- # end
161
- #
162
- # @see AttributeDSL#embedded
163
- #
164
- # @api public
165
- def wrap(*args, &block)
166
- ensure_mapper_configuration('wrap', args, block_given?)
167
-
168
- with_name_or_options(*args) do |name, options, mapper|
169
- wrap_options = { type: :hash, wrap: true }.update(options)
170
-
171
- if mapper
172
- attributes_from_mapper(mapper, name, wrap_options)
173
- else
174
- dsl(name, wrap_options, &block)
175
- end
176
- end
177
- end
178
-
179
- # Define an embedded hash attribute that requires "unwrapping" transformation
180
- #
181
- # Typically this is used in no-sql context to normalize data before
182
- # inserting to sql gateway.
183
- #
184
- # @example
185
- # dsl = AttributeDSL.new([])
186
- #
187
- # dsl.unwrap(address: [:street, :zipcode, :city])
188
- #
189
- # dsl.unwrap(:address) do
190
- # attribute :street
191
- # attribute :zipcode
192
- # attribute :city
193
- # end
194
- #
195
- # @see AttributeDSL#embedded
196
- #
197
- # @api public
198
- def unwrap(*args, &block)
199
- with_name_or_options(*args) do |name, options, mapper|
200
- unwrap_options = { type: :hash, unwrap: true }.update(options)
201
-
202
- if mapper
203
- attributes_from_mapper(mapper, name, unwrap_options)
204
- else
205
- dsl(name, unwrap_options, &block)
206
- end
207
- end
208
- end
209
-
210
- # Define an embedded hash attribute that requires "grouping" transformation
211
- #
212
- # Typically this is used in sql context when relation is a join.
213
- #
214
- # @example
215
- # dsl = AttributeDSL.new([])
216
- #
217
- # dsl.group(tags: [:name])
218
- #
219
- # dsl.group(:tags) do
220
- # model Tag
221
- # attribute :name
222
- # end
223
- #
224
- # @see AttributeDSL#embedded
225
- #
226
- # @api public
227
- def group(*args, &block)
228
- ensure_mapper_configuration('group', args, block_given?)
229
-
230
- with_name_or_options(*args) do |name, options, mapper|
231
- group_options = { type: :array, group: true }.update(options)
232
-
233
- if mapper
234
- attributes_from_mapper(mapper, name, group_options)
235
- else
236
- dsl(name, group_options, &block)
237
- end
238
- end
239
- end
240
-
241
- # Define an embedded array attribute that requires "ungrouping" transformation
242
- #
243
- # Typically this is used in non-sql context being prepared for import to sql.
244
- #
245
- # @example
246
- # dsl = AttributeDSL.new([])
247
- # dsl.ungroup(tags: [:name])
248
- #
249
- # @see AttributeDSL#embedded
250
- #
251
- # @api public
252
- def ungroup(*args, &block)
253
- with_name_or_options(*args) do |name, options, *|
254
- ungroup_options = { type: :array, ungroup: true }.update(options)
255
- dsl(name, ungroup_options, &block)
256
- end
257
- end
258
-
259
- # Define an embedded hash attribute that requires "fold" transformation
260
- #
261
- # Typically this is used in sql context to fold single joined field
262
- # to the array of values.
263
- #
264
- # @example
265
- # dsl = AttributeDSL.new([])
266
- #
267
- # dsl.fold(tags: [:name])
268
- #
269
- # @see AttributeDSL#embedded
270
- #
271
- # @api public
272
- def fold(*args, &block)
273
- with_name_or_options(*args) do |name, *|
274
- fold_options = { type: :array, fold: true }
275
- dsl(name, fold_options, &block)
276
- end
277
- end
278
-
279
- # Define an embedded hash attribute that requires "unfold" transformation
280
- #
281
- # Typically this is used in non-sql context to convert array of
282
- # values (like in Cassandra 'SET' or 'LIST' types) to array of tuples.
283
- #
284
- # Source values are assigned to the first key, the other keys being left blank.
285
- #
286
- # @example
287
- # dsl = AttributeDSL.new([])
288
- #
289
- # dsl.unfold(tags: [:name, :type], from: :tags_list)
290
- #
291
- # dsl.unfold :tags, from: :tags_list do
292
- # attribute :name, from: :tag_name
293
- # attribute :type, from: :tag_type
294
- # end
295
- #
296
- # @see AttributeDSL#embedded
297
- #
298
- # @api public
299
- def unfold(name, options = EMPTY_HASH)
300
- with_attr_options(name, options) do |attr_options|
301
- old_name = attr_options.fetch(:from, name)
302
- dsl(old_name, type: :array, unfold: true) do
303
- attribute name, attr_options
304
- yield if block_given?
305
- end
306
- end
307
- end
308
-
309
- # Define an embedded combined attribute that requires "combine" transformation
310
- #
311
- # Typically this can be used to process results of eager-loading
312
- #
313
- # @example
314
- # dsl = AttributeDSL.new([])
315
- #
316
- # dsl.combine(:tags, user_id: :id) do
317
- # model Tag
318
- #
319
- # attribute :name
320
- # end
321
- #
322
- # @param [Symbol] name
323
- # @param [Hash] options
324
- # @option options [Hash] :on The "join keys"
325
- # @option options [Symbol] :type The type, either :array (default) or :hash
326
- #
327
- # @api public
328
- def combine(name, options, &block)
329
- dsl = new(options, &block)
330
-
331
- attr_opts = {
332
- type: options.fetch(:type, :array),
333
- keys: options.fetch(:on),
334
- combine: true,
335
- header: dsl.header
336
- }
337
-
338
- add_attribute(name, attr_opts)
339
- end
340
-
341
- # Generate a header from attribute definitions
342
- #
343
- # @return [Header]
344
- #
345
- # @api private
346
- def header
347
- Header.coerce(attributes, model: model, reject_keys: reject_keys)
348
- end
349
-
350
- private
351
-
352
- # Remove the attribute used somewhere else (in wrap, group, model etc.)
353
- #
354
- # @api private
355
- def remove(*names)
356
- attributes.delete_if { |attr| names.include?(attr.first) }
357
- end
358
-
359
- # Handle attribute options common for all definitions
360
- #
361
- # @api private
362
- def with_attr_options(name, options = EMPTY_HASH)
363
- attr_options = options.dup
364
-
365
- if @prefix
366
- attr_options[:from] ||= "#{@prefix}#{@prefix_separator}#{name}"
367
- attr_options[:from] = attr_options[:from].to_sym if name.is_a? Symbol
368
- end
369
-
370
- if symbolize_keys
371
- attr_options.update(from: attr_options.fetch(:from) { name }.to_s)
372
- end
373
-
374
- yield(attr_options)
375
- end
376
-
377
- # Handle "name or options" syntax used by `wrap` and `group`
378
- #
379
- # @api private
380
- def with_name_or_options(*args)
381
- name, options =
382
- if args.size > 1
383
- args
384
- else
385
- [args.first, {}]
386
- end
387
-
388
- yield(name, options, options[:mapper])
389
- end
390
-
391
- # Create another instance of the dsl for nested definitions
392
- #
393
- # This is used by embedded, wrap and group
394
- #
395
- # @api private
396
- def dsl(name_or_attrs, options, &block)
397
- if block
398
- attributes_from_block(name_or_attrs, options, &block)
399
- else
400
- attributes_from_hash(name_or_attrs, options)
401
- end
402
- end
403
-
404
- # Define attributes from a nested block
405
- #
406
- # Used by embedded, wrap and group
407
- #
408
- # @api private
409
- def attributes_from_block(name, options, &block)
410
- dsl = new(options, &block)
411
- header = dsl.header
412
- add_attribute(name, options.update(header: header))
413
- header.each { |attr| remove(attr.key) unless name == attr.key }
414
- end
415
-
416
- # Define attributes from the `name => attributes` hash syntax
417
- #
418
- # Used by wrap and group
419
- #
420
- # @api private
421
- def attributes_from_hash(hash, options)
422
- hash.each do |name, header|
423
- with_attr_options(name, options) do |attr_options|
424
- add_attribute(name, attr_options.update(header: header.zip))
425
- header.each { |attr| remove(attr) unless name == attr }
426
- end
427
- end
428
- end
429
-
430
- # Infer mapper header for an embedded attribute
431
- #
432
- # @api private
433
- def attributes_from_mapper(mapper, name, options)
434
- if mapper.is_a?(Class)
435
- add_attribute(name, { header: mapper.header }.update(options))
436
- else
437
- raise(
438
- ArgumentError, ":mapper must be a class #{mapper.inspect}"
439
- )
440
- end
441
- end
442
-
443
- # Add a new attribute and make sure it overrides previous definition
444
- #
445
- # @api private
446
- def add_attribute(name, options)
447
- remove(name, name.to_s)
448
- attributes << [name, options]
449
- end
450
-
451
- # Create a new dsl instance of potentially overidden options
452
- #
453
- # Embedded, wrap and group can override top-level options like `prefix`
454
- #
455
- # @api private
456
- def new(options, &block)
457
- dsl = self.class.new([], @options.merge(options))
458
- dsl.instance_exec(&block) unless block.nil?
459
- dsl
460
- end
461
-
462
- # Ensure the mapping configuration isn't ambiguous
463
- #
464
- # @api private
465
- def ensure_mapper_configuration(method_name, args, block_present)
466
- if args.first.is_a?(Hash) && block_present
467
- raise MapperMisconfiguredError,
468
- "Cannot configure `#{method_name}#` using both options and a block"
469
- end
470
- if args.first.is_a?(Hash) && args.first[:mapper]
471
- raise MapperMisconfiguredError,
472
- "Cannot configure `#{method_name}#` using both options and a mapper"
473
- end
474
- end
475
- end
476
- end
477
- end