dm-core 1.1.0 → 1.2.0.rc1

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 (104) hide show
  1. data/Gemfile +13 -11
  2. data/README.rdoc +1 -1
  3. data/Rakefile +1 -2
  4. data/VERSION +1 -1
  5. data/dm-core.gemspec +30 -176
  6. data/lib/dm-core.rb +32 -67
  7. data/lib/dm-core/adapters/abstract_adapter.rb +1 -2
  8. data/lib/dm-core/associations/many_to_many.rb +11 -5
  9. data/lib/dm-core/associations/many_to_one.rb +17 -2
  10. data/lib/dm-core/associations/one_to_many.rb +16 -0
  11. data/lib/dm-core/backwards.rb +13 -0
  12. data/lib/dm-core/collection.rb +1 -1
  13. data/lib/dm-core/model.rb +99 -41
  14. data/lib/dm-core/model/property.rb +24 -27
  15. data/lib/dm-core/model/relationship.rb +22 -28
  16. data/lib/dm-core/property.rb +37 -50
  17. data/lib/dm-core/property/boolean.rb +6 -10
  18. data/lib/dm-core/property/date.rb +0 -2
  19. data/lib/dm-core/property/date_time.rb +0 -2
  20. data/lib/dm-core/property/decimal.rb +5 -1
  21. data/lib/dm-core/property/discriminator.rb +24 -26
  22. data/lib/dm-core/property/float.rb +5 -1
  23. data/lib/dm-core/property/numeric.rb +6 -9
  24. data/lib/dm-core/property/string.rb +2 -1
  25. data/lib/dm-core/property/time.rb +0 -2
  26. data/lib/dm-core/property/typecast/time.rb +7 -2
  27. data/lib/dm-core/property_set.rb +1 -3
  28. data/lib/dm-core/query.rb +3 -10
  29. data/lib/dm-core/query/conditions/comparison.rb +5 -1
  30. data/lib/dm-core/query/conditions/operation.rb +1 -1
  31. data/lib/dm-core/relationship_set.rb +0 -2
  32. data/lib/dm-core/resource.rb +27 -28
  33. data/lib/dm-core/resource/{state.rb → persistence_state.rb} +2 -2
  34. data/lib/dm-core/resource/{state → persistence_state}/clean.rb +4 -4
  35. data/lib/dm-core/resource/{state → persistence_state}/deleted.rb +2 -2
  36. data/lib/dm-core/resource/{state → persistence_state}/dirty.rb +2 -2
  37. data/lib/dm-core/resource/{state → persistence_state}/immutable.rb +3 -3
  38. data/lib/dm-core/resource/{state → persistence_state}/persisted.rb +3 -3
  39. data/lib/dm-core/resource/{state → persistence_state}/transient.rb +3 -3
  40. data/lib/dm-core/spec/lib/adapter_helpers.rb +2 -5
  41. data/lib/dm-core/spec/setup.rb +3 -2
  42. data/lib/dm-core/spec/shared/public/property_spec.rb +8 -0
  43. data/lib/dm-core/spec/shared/resource_spec.rb +14 -0
  44. data/lib/dm-core/spec/shared/semipublic/property_spec.rb +1 -1
  45. data/lib/dm-core/support/descendant_set.rb +0 -2
  46. data/lib/dm-core/support/ext/array.rb +0 -19
  47. data/lib/dm-core/support/ext/blank.rb +1 -0
  48. data/lib/dm-core/support/hook.rb +0 -3
  49. data/lib/dm-core/support/naming_conventions.rb +6 -0
  50. data/lib/dm-core/support/ordered_set.rb +0 -2
  51. data/lib/dm-core/support/subject_set.rb +0 -2
  52. data/lib/dm-core/version.rb +1 -1
  53. data/spec/public/associations/many_to_many_spec.rb +0 -1
  54. data/spec/public/model/property_spec.rb +55 -9
  55. data/spec/public/model/relationship_spec.rb +24 -2
  56. data/spec/public/model_spec.rb +32 -0
  57. data/spec/public/property/binary_spec.rb +14 -6
  58. data/spec/public/property/boolean_spec.rb +14 -6
  59. data/spec/public/property/class_spec.rb +14 -6
  60. data/spec/public/property/date_spec.rb +14 -6
  61. data/spec/public/property/date_time_spec.rb +14 -6
  62. data/spec/public/property/decimal_spec.rb +10 -2
  63. data/spec/public/property/discriminator_spec.rb +15 -1
  64. data/spec/public/property/float_spec.rb +14 -6
  65. data/spec/public/property/integer_spec.rb +14 -6
  66. data/spec/public/property/object_spec.rb +8 -0
  67. data/spec/public/property/serial_spec.rb +14 -6
  68. data/spec/public/property/string_spec.rb +14 -6
  69. data/spec/public/property/text_spec.rb +14 -6
  70. data/spec/public/property/time_spec.rb +14 -6
  71. data/spec/public/resource_spec.rb +58 -0
  72. data/spec/public/shared/finder_shared_spec.rb +8 -4
  73. data/spec/semipublic/associations/many_to_many_spec.rb +2 -0
  74. data/spec/semipublic/associations/many_to_one_spec.rb +2 -0
  75. data/spec/semipublic/associations/one_to_many_spec.rb +2 -0
  76. data/spec/semipublic/associations/one_to_one_spec.rb +2 -0
  77. data/spec/semipublic/property/binary_spec.rb +5 -5
  78. data/spec/semipublic/property/boolean_spec.rb +5 -5
  79. data/spec/semipublic/property/class_spec.rb +5 -5
  80. data/spec/semipublic/property/date_spec.rb +5 -5
  81. data/spec/semipublic/property/date_time_spec.rb +5 -5
  82. data/spec/semipublic/property/decimal_spec.rb +2 -2
  83. data/spec/semipublic/property/discriminator_spec.rb +5 -5
  84. data/spec/semipublic/property/float_spec.rb +5 -5
  85. data/spec/semipublic/property/integer_spec.rb +5 -5
  86. data/spec/semipublic/property/lookup_spec.rb +3 -3
  87. data/spec/semipublic/property/serial_spec.rb +5 -5
  88. data/spec/semipublic/property/string_spec.rb +5 -5
  89. data/spec/semipublic/property/text_spec.rb +5 -5
  90. data/spec/semipublic/property/time_spec.rb +5 -5
  91. data/spec/semipublic/query/conditions/comparison_spec.rb +44 -4
  92. data/spec/semipublic/query_spec.rb +2 -11
  93. data/spec/semipublic/resource/state/clean_spec.rb +6 -6
  94. data/spec/semipublic/resource/state/deleted_spec.rb +4 -4
  95. data/spec/semipublic/resource/state/dirty_spec.rb +8 -8
  96. data/spec/semipublic/resource/state/immutable_spec.rb +6 -6
  97. data/spec/semipublic/resource/state/transient_spec.rb +5 -5
  98. data/spec/semipublic/resource/state_spec.rb +15 -15
  99. data/spec/semipublic/shared/resource_shared_spec.rb +7 -1
  100. data/spec/semipublic/shared/resource_state_shared_spec.rb +8 -8
  101. data/spec/unit/array_spec.rb +0 -14
  102. data/spec/unit/blank_spec.rb +11 -0
  103. metadata +70 -188
  104. data/lib/dm-core/support/inflector.rb +0 -3
@@ -1,15 +1,12 @@
1
1
  # TODO: update Model#respond_to? to return true if method_method missing
2
2
  # would handle the message
3
3
 
4
- require 'dm-core/relationship_set'
5
-
6
4
  module DataMapper
7
5
  module Model
8
6
  module Relationship
9
7
  Model.append_extensions self
10
8
 
11
9
  include DataMapper::Assertions
12
- extend Chainable
13
10
 
14
11
  # Initializes relationships hash for extended model
15
12
  # class.
@@ -23,21 +20,19 @@ module DataMapper
23
20
  model.instance_variable_set(:@relationships, {})
24
21
  end
25
22
 
26
- chainable do
27
- # When DataMapper model is inherited, relationships
28
- # of parent are duplicated and copied to subclass model
29
- #
30
- # @api private
31
- def inherited(model)
32
- model.instance_variable_set(:@relationships, {})
33
-
34
- @relationships.each do |repository_name, relationships|
35
- model_relationships = model.relationships(repository_name)
36
- relationships.each { |relationship| model_relationships << relationship }
37
- end
23
+ # When DataMapper model is inherited, relationships
24
+ # of parent are duplicated and copied to subclass model
25
+ #
26
+ # @api private
27
+ def inherited(model)
28
+ model.instance_variable_set(:@relationships, {})
38
29
 
39
- super
30
+ @relationships.each do |repository_name, relationships|
31
+ model_relationships = model.relationships(repository_name)
32
+ relationships.each { |relationship| model_relationships << relationship }
40
33
  end
34
+
35
+ super
41
36
  end
42
37
 
43
38
  # Returns copy of relationships set in given repository.
@@ -194,7 +189,7 @@ module DataMapper
194
189
  relationship
195
190
  end
196
191
 
197
- private
192
+ private
198
193
 
199
194
  # Extract the model from an Array of arguments
200
195
  #
@@ -342,7 +337,7 @@ module DataMapper
342
337
  # of 1:1, the underlying collection is hidden in a
343
338
  # private ivar, and the resource is in a known ivar
344
339
 
345
- persisted_state.get(relationships[#{name.inspect}], query)
340
+ persistence_state.get(relationships[#{name.inspect}], query)
346
341
  end
347
342
  RUBY
348
343
  end
@@ -362,22 +357,21 @@ module DataMapper
362
357
  #{writer_visibility}
363
358
  def #{writer_name}(target)
364
359
  relationship = relationships[#{name.inspect}]
365
- self.persisted_state = persisted_state.set(relationship, target)
366
- persisted_state.get(relationship)
360
+ self.persistence_state = persistence_state.set(relationship, target)
361
+ persistence_state.get(relationship)
367
362
  end
368
363
  RUBY
369
364
  end
370
365
 
371
- chainable do
372
- # @api public
373
- def method_missing(method, *args, &block)
374
- if relationship = relationships(repository_name)[method]
375
- return Query::Path.new([ relationship ])
376
- end
377
-
378
- super
366
+ # @api public
367
+ def method_missing(method, *args, &block)
368
+ if relationship = relationships(repository_name)[method]
369
+ return Query::Path.new([ relationship ])
379
370
  end
371
+
372
+ super
380
373
  end
374
+
381
375
  end # module Relationship
382
376
  end # module Model
383
377
  end # module DataMapper
@@ -1,6 +1,3 @@
1
- require 'dm-core/resource'
2
- require 'dm-core/query'
3
-
4
1
  module DataMapper
5
2
  # = Properties
6
3
  # Properties for a model are not derived from a database structure, but
@@ -83,11 +80,13 @@ module DataMapper
83
80
  # property :title, String
84
81
  #
85
82
  # def title=(new_title)
86
- # raise ArgumentError if new_title != 'Luke is Awesome'
87
- # @title = new_title
83
+ # raise ArgumentError if new_title != 'Lee is l337'
84
+ # super(new_title)
88
85
  # end
89
86
  # end
90
87
  #
88
+ # Calling super ensures that any validators defined for the property are kept active.
89
+ #
91
90
  # == Lazy Loading
92
91
  # By default, some properties are not loaded when an object is fetched in
93
92
  # DataMapper. These lazily loaded properties are fetched on demand when their
@@ -107,7 +106,7 @@ module DataMapper
107
106
  #
108
107
  # If you want to over-ride the lazy loading on any field you can set it to a
109
108
  # context or false to disable it with the :lazy option. Contexts allow
110
- # multipule lazy properties to be loaded at one time. If you set :lazy to
109
+ # multiple lazy properties to be loaded at one time. If you set :lazy to
111
110
  # true, it is placed in the :default context
112
111
  #
113
112
  # class Post
@@ -179,44 +178,40 @@ module DataMapper
179
178
  #
180
179
  # == Inferred Validations
181
180
  # If you require the dm-validations plugin, auto-validations will
182
- # automatically be mixed-in in to your model classes:
183
- # validation rules that are inferred when properties are declared with
184
- # specific column restrictions.
181
+ # automatically be mixed-in in to your model classes: validation rules that
182
+ # are inferred when properties are declared with specific column restrictions.
185
183
  #
186
- # class Post
187
- # include DataMapper::Resource
188
- #
189
- # property :title, String, :length => 250
190
- # # => infers 'validates_length :title,
191
- # :minimum => 0, :maximum => 250'
184
+ # class Post
185
+ # include DataMapper::Resource
192
186
  #
193
- # property :title, String, :required => true
194
- # # => infers 'validates_present :title
187
+ # property :title, String, :length => 250, :min => 0, :max => 250
188
+ # # => infers 'validates_length :title'
195
189
  #
196
- # property :email, String, :format => :email_address
197
- # # => infers 'validates_format :email, :with => :email_address
190
+ # property :title, String, :required => true
191
+ # # => infers 'validates_present :title'
198
192
  #
199
- # property :title, String, :length => 255, :required => true
200
- # # => infers both 'validates_length' as well as
201
- # # 'validates_present'
202
- # # better: property :title, String, :length => 1..255
193
+ # property :email, String, :format => :email_address
194
+ # # => infers 'validates_format :email, :with => :email_address'
203
195
  #
204
- # end
196
+ # property :title, String, :length => 255, :required => true
197
+ # # => infers both 'validates_length' as well as 'validates_present'
198
+ # # better: property :title, String, :length => 1..255
199
+ # end
205
200
  #
206
201
  # This functionality is available with the dm-validations gem. For more information
207
202
  # about validations, check the documentation for dm-validations.
208
203
  #
209
204
  # == Default Values
210
- # To set a default for a property, use the <tt>:default</tt> key. The
205
+ # To set a default for a property, use the <tt>:default</tt> key. The
211
206
  # property will be set to the value associated with that key the first time
212
207
  # it is accessed, or when the resource is saved if it hasn't been set with
213
- # another value already. This value can be a static value, such as 'hello'
208
+ # another value already. This value can be a static value, such as 'hello'
214
209
  # but it can also be a proc that will be evaluated when the property is read
215
- # before its value has been set. The property is set to the return of the
216
- # proc. The proc is passed two values, the resource the property is being set
210
+ # before its value has been set. The property is set to the return of the
211
+ # proc. The proc is passed two values, the resource the property is being set
217
212
  # for and the property itself.
218
213
  #
219
- # property :display_name, String, :default => { |resource, property| resource.login }
214
+ # property :display_name, String, :default => lambda { |resource, property| resource.login }
220
215
  #
221
216
  # Word of warning. Don't try to read the value of the property you're setting
222
217
  # the default for in the proc. An infinite loop will ensue.
@@ -270,9 +265,6 @@ module DataMapper
270
265
  # Only makes sense for float type properties. Must be > 0.
271
266
  # Default is nil for Float type and 10 for BigDecimal
272
267
  #
273
- # All other keys you pass to +property+ method are stored and available
274
- # as options[:extra_keys].
275
- #
276
268
  # == Overriding default Property options
277
269
  #
278
270
  # There is the ability to reconfigure a Property and it's subclasses by explicitly
@@ -291,7 +283,7 @@ module DataMapper
291
283
  # DataMapper::Property.auto_validation(false)
292
284
  #
293
285
  # # set all mutator methods to be private by default
294
- # DataMapper::Property.writer(false)
286
+ # DataMapper::Property.writer(:private)
295
287
  #
296
288
  # Please note that this has no effect when a subclass has explicitly
297
289
  # defined it's own option. For example, setting the String length to
@@ -311,8 +303,7 @@ module DataMapper
311
303
  module PassThroughLoadDump
312
304
  # @api semipublic
313
305
  def load(value)
314
- return if value.nil?
315
- typecast(value)
306
+ typecast(value) unless value.nil?
316
307
  end
317
308
 
318
309
  # Stub instance method for dumping
@@ -329,10 +320,9 @@ module DataMapper
329
320
 
330
321
  include DataMapper::Assertions
331
322
  include Subject
332
- extend Chainable
333
323
  extend Equalizer
334
324
 
335
- equalize :model, :name
325
+ equalize :model, :name, :options
336
326
 
337
327
  PRIMITIVES = [
338
328
  TrueClass,
@@ -357,7 +347,10 @@ module DataMapper
357
347
  VISIBILITY_OPTIONS = [ :public, :protected, :private ].to_set.freeze
358
348
 
359
349
  # Invalid property names
360
- INVALID_NAMES = (Resource.instance_methods + Resource.private_instance_methods + Query::OPTIONS.to_a).map { |name| name.to_s }.freeze
350
+ INVALID_NAMES = (Resource.instance_methods +
351
+ Resource.private_instance_methods +
352
+ Query::OPTIONS.to_a
353
+ ).map { |name| name.to_s }
361
354
 
362
355
  attr_reader :primitive, :model, :name, :instance_variable_name,
363
356
  :reader_visibility, :writer_visibility, :options,
@@ -462,9 +455,8 @@ module DataMapper
462
455
  # @api public
463
456
  def options
464
457
  options = {}
465
- accepted_options.each do |method|
466
- value = send(method)
467
- options[method] = value unless value.nil?
458
+ accepted_options.each do |name|
459
+ options[name] = send(name) if instance_variable_defined?("@#{name}")
468
460
  end
469
461
  options
470
462
  end
@@ -737,20 +729,15 @@ module DataMapper
737
729
  value.kind_of?(primitive)
738
730
  end
739
731
 
740
- chainable do
741
- def self.new(model, name, options = {})
742
- super
743
- end
744
- end
745
-
746
- protected
732
+ protected
747
733
 
748
734
  # @api semipublic
749
735
  def initialize(model, name, options = {})
750
736
  options = options.to_hash.dup
751
737
 
752
- if INVALID_NAMES.include?(name.to_s)
753
- raise ArgumentError, "+name+ was #{name.inspect}, which cannot be used as a property name since it collides with an existing method or a query option"
738
+ if INVALID_NAMES.include?(name.to_s) || (kind_of?(Boolean) && INVALID_NAMES.include?("#{name}?"))
739
+ raise ArgumentError,
740
+ "+name+ was #{name.inspect}, which cannot be used as a property name since it collides with an existing method or a query option"
754
741
  end
755
742
 
756
743
  assert_valid_options(options)
@@ -5,6 +5,11 @@ module DataMapper
5
5
 
6
6
  primitive ::TrueClass
7
7
 
8
+ TRUE_VALUES = [ 1, '1', 't', 'T', 'true', 'TRUE' ].freeze
9
+ FALSE_VALUES = [ 0, '0', 'f', 'F', 'false', 'FALSE' ].freeze
10
+ BOOLEAN_MAP = Hash[
11
+ TRUE_VALUES.product([ true ]) + FALSE_VALUES.product([ false ]) ].freeze
12
+
8
13
  def primitive?(value)
9
14
  value == true || value == false
10
15
  end
@@ -19,16 +24,7 @@ module DataMapper
19
24
  #
20
25
  # @api private
21
26
  def typecast_to_primitive(value)
22
- if value.kind_of?(::Integer)
23
- return true if value == 1
24
- return false if value == 0
25
- elsif value.respond_to?(:to_str)
26
- string_value = value.to_str.downcase
27
- return true if %w[ true 1 t ].include?(string_value)
28
- return false if %w[ false 0 f ].include?(string_value)
29
- end
30
-
31
- value
27
+ BOOLEAN_MAP.fetch(value, value)
32
28
  end
33
29
  end # class Boolean
34
30
  end # class Property
@@ -1,5 +1,3 @@
1
- require 'dm-core/property/typecast/time'
2
-
3
1
  module DataMapper
4
2
  class Property
5
3
  class Date < Object
@@ -1,5 +1,3 @@
1
- require 'dm-core/property/typecast/time'
2
-
3
1
  module DataMapper
4
2
  class Property
5
3
  class DateTime < Object
@@ -3,7 +3,11 @@ module DataMapper
3
3
  class Decimal < Numeric
4
4
  primitive BigDecimal
5
5
 
6
- DEFAULT_SCALE = 0
6
+ DEFAULT_PRECISION = 10
7
+ DEFAULT_SCALE = 0
8
+
9
+ precision(DEFAULT_PRECISION)
10
+ scale(DEFAULT_SCALE)
7
11
 
8
12
  protected
9
13
 
@@ -8,40 +8,38 @@ module DataMapper
8
8
 
9
9
  # @api private
10
10
  def bind
11
- model.default_scope(repository_name).update(name => model.descendants.dup << model)
12
-
13
- model.class_eval <<-RUBY, __FILE__, __LINE__ + 1
14
- extend Chainable
11
+ model.extend Model unless model < Model
12
+ end
15
13
 
16
- extendable do
17
- def inherited(model)
18
- super # setup self.descendants
19
- set_discriminator_scope_for(model)
20
- end
14
+ module Model
15
+ def inherited(model)
16
+ super # setup self.descendants
17
+ set_discriminator_scope_for(model)
18
+ end
21
19
 
22
- def new(*args, &block)
23
- if args.size == 1 && args.first.kind_of?(Hash)
24
- discriminator = properties(repository_name).discriminator
20
+ def new(*args, &block)
21
+ if args.size == 1 && args.first.kind_of?(Hash)
22
+ discriminator = properties(repository_name).discriminator
25
23
 
26
- if discriminator_value = args.first[discriminator.name]
27
- model = discriminator.typecast_to_primitive(discriminator_value)
24
+ if discriminator_value = args.first[discriminator.name]
25
+ model = discriminator.typecast_to_primitive(discriminator_value)
28
26
 
29
- if model.kind_of?(Model) && !model.equal?(self)
30
- return model.new(*args, &block)
31
- end
32
- end
27
+ if model.kind_of?(Model) && !model.equal?(self)
28
+ return model.new(*args, &block)
33
29
  end
34
-
35
- super
36
30
  end
31
+ end
37
32
 
38
- private
33
+ super
34
+ end
39
35
 
40
- def set_discriminator_scope_for(model)
41
- model.default_scope(#{repository_name.inspect}).update(#{name.inspect} => model.descendants.dup << model)
42
- end
43
- end
44
- RUBY
36
+ private
37
+
38
+ def set_discriminator_scope_for(model)
39
+ discriminator = self.properties.discriminator
40
+ default_scope = model.default_scope(discriminator.repository_name)
41
+ default_scope.update(discriminator.name => model.descendants.dup << model)
42
+ end
45
43
  end
46
44
  end # class Discriminator
47
45
  end # module Property
@@ -3,7 +3,11 @@ module DataMapper
3
3
  class Float < Numeric
4
4
  primitive ::Float
5
5
 
6
- DEFAULT_SCALE = nil
6
+ DEFAULT_PRECISION = 10
7
+ DEFAULT_SCALE = nil
8
+
9
+ precision(DEFAULT_PRECISION)
10
+ scale(DEFAULT_SCALE)
7
11
 
8
12
  protected
9
13
 
@@ -1,5 +1,3 @@
1
- require 'dm-core/property/typecast/numeric'
2
-
3
1
  module DataMapper
4
2
  class Property
5
3
  class Numeric < Object
@@ -9,9 +7,8 @@ module DataMapper
9
7
  accept_options :precision, :scale, :min, :max
10
8
  attr_reader :precision, :scale, :min, :max
11
9
 
12
- DEFAULT_PRECISION = 10
13
- DEFAULT_NUMERIC_MIN = 0
14
- DEFAULT_NUMERIC_MAX = 2**31-1
10
+ DEFAULT_NUMERIC_MIN = 0
11
+ DEFAULT_NUMERIC_MAX = 2**31-1
15
12
 
16
13
  protected
17
14
 
@@ -19,8 +16,8 @@ module DataMapper
19
16
  super
20
17
 
21
18
  if @primitive == BigDecimal || @primitive == ::Float
22
- @precision = @options.fetch(:precision, DEFAULT_PRECISION)
23
- @scale = @options.fetch(:scale, self.class::DEFAULT_SCALE)
19
+ @precision = @options.fetch(:precision)
20
+ @scale = @options.fetch(:scale)
24
21
 
25
22
  unless @precision > 0
26
23
  raise ArgumentError, "precision must be greater than 0, but was #{@precision.inspect}"
@@ -28,8 +25,8 @@ module DataMapper
28
25
  end
29
26
 
30
27
  if @options.key?(:min) || @options.key?(:max)
31
- @min = @options.fetch(:min, DEFAULT_NUMERIC_MIN)
32
- @max = @options.fetch(:max, DEFAULT_NUMERIC_MAX)
28
+ @min = @options.fetch(:min, self.class::DEFAULT_NUMERIC_MIN)
29
+ @max = @options.fetch(:max, self.class::DEFAULT_NUMERIC_MAX)
33
30
 
34
31
  if @max < DEFAULT_NUMERIC_MIN && !@options.key?(:min)
35
32
  raise ArgumentError, "min should be specified when the max is less than #{DEFAULT_NUMERIC_MIN}"