rom-mapper 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +2 -0
  3. data/.rspec +1 -1
  4. data/.travis.yml +19 -13
  5. data/{Changelog.md → CHANGELOG.md} +6 -0
  6. data/Gemfile +23 -10
  7. data/README.md +17 -12
  8. data/Rakefile +12 -4
  9. data/lib/rom-mapper.rb +6 -15
  10. data/lib/rom/header.rb +195 -0
  11. data/lib/rom/header/attribute.rb +184 -0
  12. data/lib/rom/mapper.rb +63 -100
  13. data/lib/rom/mapper/attribute_dsl.rb +477 -0
  14. data/lib/rom/mapper/dsl.rb +120 -0
  15. data/lib/rom/mapper/model_dsl.rb +55 -0
  16. data/lib/rom/mapper/version.rb +3 -7
  17. data/lib/rom/model_builder.rb +99 -0
  18. data/lib/rom/processor.rb +28 -0
  19. data/lib/rom/processor/transproc.rb +388 -0
  20. data/rakelib/benchmark.rake +15 -0
  21. data/rakelib/mutant.rake +16 -0
  22. data/rakelib/rubocop.rake +18 -0
  23. data/rom-mapper.gemspec +7 -6
  24. data/spec/spec_helper.rb +32 -33
  25. data/spec/support/constant_leak_finder.rb +14 -0
  26. data/spec/support/mutant.rb +10 -0
  27. data/spec/unit/rom/mapper/dsl_spec.rb +467 -0
  28. data/spec/unit/rom/mapper_spec.rb +83 -0
  29. data/spec/unit/rom/processor/transproc_spec.rb +448 -0
  30. metadata +68 -89
  31. data/.ruby-version +0 -1
  32. data/Gemfile.devtools +0 -55
  33. data/config/devtools.yml +0 -2
  34. data/config/flay.yml +0 -3
  35. data/config/flog.yml +0 -2
  36. data/config/mutant.yml +0 -3
  37. data/config/reek.yml +0 -103
  38. data/config/rubocop.yml +0 -45
  39. data/lib/rom/mapper/attribute.rb +0 -31
  40. data/lib/rom/mapper/dumper.rb +0 -27
  41. data/lib/rom/mapper/loader.rb +0 -22
  42. data/lib/rom/mapper/loader/allocator.rb +0 -32
  43. data/lib/rom/mapper/loader/attribute_writer.rb +0 -23
  44. data/lib/rom/mapper/loader/object_builder.rb +0 -28
  45. data/spec/shared/unit/loader_call.rb +0 -13
  46. data/spec/shared/unit/loader_identity.rb +0 -13
  47. data/spec/shared/unit/mapper_context.rb +0 -13
  48. data/spec/unit/rom/mapper/call_spec.rb +0 -32
  49. data/spec/unit/rom/mapper/class_methods/build_spec.rb +0 -64
  50. data/spec/unit/rom/mapper/dump_spec.rb +0 -15
  51. data/spec/unit/rom/mapper/dumper/call_spec.rb +0 -29
  52. data/spec/unit/rom/mapper/dumper/identity_spec.rb +0 -28
  53. data/spec/unit/rom/mapper/header/each_spec.rb +0 -28
  54. data/spec/unit/rom/mapper/header/element_reader_spec.rb +0 -25
  55. data/spec/unit/rom/mapper/header/keys_spec.rb +0 -32
  56. data/spec/unit/rom/mapper/identity_from_tuple_spec.rb +0 -15
  57. data/spec/unit/rom/mapper/identity_spec.rb +0 -15
  58. data/spec/unit/rom/mapper/load_spec.rb +0 -15
  59. data/spec/unit/rom/mapper/loader/allocator/call_spec.rb +0 -7
  60. data/spec/unit/rom/mapper/loader/allocator/identity_spec.rb +0 -7
  61. data/spec/unit/rom/mapper/loader/attribute_writer/call_spec.rb +0 -7
  62. data/spec/unit/rom/mapper/loader/attribute_writer/identity_spec.rb +0 -7
  63. data/spec/unit/rom/mapper/loader/object_builder/call_spec.rb +0 -7
  64. data/spec/unit/rom/mapper/loader/object_builder/identity_spec.rb +0 -7
  65. data/spec/unit/rom/mapper/model_spec.rb +0 -11
  66. data/spec/unit/rom/mapper/new_object_spec.rb +0 -14
data/lib/rom/mapper.rb CHANGED
@@ -1,135 +1,98 @@
1
- # encoding: utf-8
1
+ require 'rom/mapper/dsl'
2
2
 
3
- module ROM
3
+ require 'rom/support/inheritance_hook'
4
4
 
5
- # Mappers load tuples into objects and dump objects back into tuples
5
+ module ROM
6
+ # Mapper is a simple object that uses transformers to load relations
6
7
  #
8
+ # @private
7
9
  class Mapper
8
- include Concord::Public.new(:header, :loader, :dumper)
9
-
10
- LOADERS = {
11
- allocator: Loader::Allocator,
12
- object_builder: Loader::ObjectBuilder,
13
- attribute_writer: Loader::AttributeWriter
14
- }
10
+ extend ROM::Support::InheritanceHook
11
+ include DSL
12
+ include Equalizer.new(:transformers, :header)
15
13
 
16
- DUMPERS = {
17
- default: Dumper
18
- }
14
+ defines :relation, :register_as, :symbolize_keys,
15
+ :prefix, :prefix_separator, :inherit_header, :reject_keys
19
16
 
20
- DEFAULT_LOADER = :allocator
21
- DEFAULT_DUMPER = :default
17
+ inherit_header true
18
+ reject_keys false
19
+ prefix_separator '_'.freeze
22
20
 
23
- # Build a mapper
24
- #
25
- # @example
26
- #
27
- # header = Mapper::Header.build([[:user_name, String]], map: { user_name: :name })
28
- #
29
- # mapper = Mapper.build(header, User)
30
- # mapper = Mapper.build(header, User, loader_class: Loader::ObjectBuilder)
21
+ # @return [Object] transformers object built by a processor
31
22
  #
32
- # @param [Header]
33
- # @param [Class]
34
- # @param [Hash]
35
- #
36
- # @return [Mapper]
37
- #
38
- # @api public
39
- def self.build(header, model, options = {})
40
- loader_class = LOADERS[options.fetch(:loader, DEFAULT_LOADER)]
41
- dumper_class = DUMPERS[options.fetch(:dumper, DEFAULT_DUMPER)]
23
+ # @api private
24
+ attr_reader :transformers
42
25
 
43
- header = Header.build(header, options)
44
- loader = loader_class.new(header, model)
45
- dumper = dumper_class.new(header)
26
+ # @return [Header] header that was used to build the transformers
27
+ #
28
+ # @api private
29
+ attr_reader :header
46
30
 
47
- new(header, loader, dumper)
31
+ # @return [Hash] registered processors
32
+ #
33
+ # @api private
34
+ def self.processors
35
+ @_processors ||= {}
48
36
  end
49
37
 
50
- # Project and rename given relation
51
- #
52
- # @example
53
- #
54
- # mapper.call(relation)
38
+ # Register a processor class
55
39
  #
56
- # @param [Axiom::Relation]
40
+ # @return [Hash]
57
41
  #
58
- # @return [Axiom::Relation]
59
- #
60
- # @api public
61
- def call(relation)
62
- mapping = header.mapping
63
- attributes = mapping.keys
64
-
65
- relation.project(attributes).rename(mapping)
42
+ # @api private
43
+ def self.register_processor(processor)
44
+ name = processor.name.split('::').last.downcase.to_sym
45
+ processors.update(name => processor)
66
46
  end
67
47
 
68
- # Retrieve identity from the given object
69
- #
70
- # @example
48
+ # Prepares an array of headers for a potentially multistep mapper
71
49
  #
72
- # mapper.identity(user) # => [1]
50
+ # @return [Array<Header>]
73
51
  #
74
- # @param [Object]
75
- #
76
- # @return [Array]
77
- #
78
- # @api public
79
- def identity(object)
80
- dumper.identity(object)
52
+ # @api private
53
+ def self.headers(header)
54
+ return [header] if steps.empty?
55
+ return steps.map(&:header) if attributes.empty?
56
+ raise(MapperMisconfiguredError, "cannot mix outer attributes and steps")
81
57
  end
82
58
 
83
- # Return identity from the given tuple
84
- #
85
- # @example
86
- #
87
- # mapper.identity_from_tuple({id: 1}) # => [1]
59
+ # Build a mapper using provided processor type
88
60
  #
89
- # @param [Axiom::Tuple,Hash]
90
- #
91
- # @return [Array]
61
+ # @return [Mapper]
92
62
  #
93
- # @api public
94
- def identity_from_tuple(tuple)
95
- loader.identity(tuple)
63
+ # @api private
64
+ def self.build(header = self.header, processor = :transproc)
65
+ processor = Mapper.processors.fetch(processor)
66
+ transformers = headers(header).map(&processor.method(:build))
67
+ new(transformers, header)
96
68
  end
97
69
 
98
- # Build a new model instance
99
- #
100
- # @example
101
- #
102
- # mapper = Mapper.build(header, User)
103
- # mapper.new_object(id: 1, name: 'Jane') # => #<User @id=1 @name="Jane">
104
- #
105
- # @api public
106
- def new_object(*args, &block)
107
- model.new(*args, &block)
70
+ # @api private
71
+ def self.registry(descendants)
72
+ descendants.each_with_object({}) do |klass, h|
73
+ name = klass.register_as || klass.relation
74
+ (h[klass.base_relation] ||= {})[name] = klass.build
75
+ end
108
76
  end
109
77
 
110
- # Return model used by this mapper
111
- #
112
- # @return [Class]
113
- #
114
- # @api public
115
- def model
116
- loader.model
78
+ # @api private
79
+ def initialize(transformers, header)
80
+ @transformers = Array(transformers)
81
+ @header = header
117
82
  end
118
83
 
119
- # Load an object instance from the tuple
84
+ # @return [Class] optional model that is instantiated by a mapper
120
85
  #
121
86
  # @api private
122
- def load(tuple)
123
- loader.call(tuple)
87
+ def model
88
+ header.model
124
89
  end
125
90
 
126
- # Dump an object into a tuple
91
+ # Process a relation using the transformers
127
92
  #
128
93
  # @api private
129
- def dump(object)
130
- dumper.call(object)
94
+ def call(relation)
95
+ transformers.reduce(relation.to_a) { |a, e| e.call(a) }
131
96
  end
132
-
133
- end # Mapper
134
-
135
- end # ROM
97
+ end
98
+ end
@@ -0,0 +1,477 @@
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