rom 0.8.1 → 0.9.0.beta1

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.
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