activemodel 4.2.11.3 → 5.0.7.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +149 -56
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +8 -16
  5. data/lib/active_model/attribute_assignment.rb +52 -0
  6. data/lib/active_model/attribute_methods.rb +16 -16
  7. data/lib/active_model/callbacks.rb +3 -3
  8. data/lib/active_model/conversion.rb +5 -5
  9. data/lib/active_model/dirty.rb +41 -40
  10. data/lib/active_model/errors.rb +175 -68
  11. data/lib/active_model/forbidden_attributes_protection.rb +3 -2
  12. data/lib/active_model/gem_version.rb +5 -5
  13. data/lib/active_model/lint.rb +32 -28
  14. data/lib/active_model/locale/en.yml +2 -1
  15. data/lib/active_model/model.rb +3 -4
  16. data/lib/active_model/naming.rb +5 -4
  17. data/lib/active_model/secure_password.rb +2 -9
  18. data/lib/active_model/serialization.rb +36 -9
  19. data/lib/active_model/type/big_integer.rb +13 -0
  20. data/lib/active_model/type/binary.rb +50 -0
  21. data/lib/active_model/type/boolean.rb +21 -0
  22. data/lib/active_model/type/date.rb +54 -0
  23. data/lib/active_model/type/date_time.rb +44 -0
  24. data/lib/active_model/type/decimal.rb +66 -0
  25. data/lib/active_model/type/decimal_without_scale.rb +11 -0
  26. data/lib/active_model/type/float.rb +25 -0
  27. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +35 -0
  28. data/lib/active_model/type/helpers/mutable.rb +18 -0
  29. data/lib/active_model/type/helpers/numeric.rb +34 -0
  30. data/lib/active_model/type/helpers/time_value.rb +77 -0
  31. data/lib/active_model/type/helpers.rb +4 -0
  32. data/lib/active_model/type/immutable_string.rb +29 -0
  33. data/lib/active_model/type/integer.rb +66 -0
  34. data/lib/active_model/type/registry.rb +64 -0
  35. data/lib/active_model/type/string.rb +24 -0
  36. data/lib/active_model/type/text.rb +11 -0
  37. data/lib/active_model/type/time.rb +42 -0
  38. data/lib/active_model/type/unsigned_integer.rb +15 -0
  39. data/lib/active_model/type/value.rb +116 -0
  40. data/lib/active_model/type.rb +59 -0
  41. data/lib/active_model/validations/absence.rb +1 -1
  42. data/lib/active_model/validations/acceptance.rb +57 -9
  43. data/lib/active_model/validations/callbacks.rb +3 -3
  44. data/lib/active_model/validations/clusivity.rb +4 -3
  45. data/lib/active_model/validations/confirmation.rb +16 -4
  46. data/lib/active_model/validations/exclusion.rb +3 -1
  47. data/lib/active_model/validations/format.rb +1 -1
  48. data/lib/active_model/validations/helper_methods.rb +13 -0
  49. data/lib/active_model/validations/inclusion.rb +3 -3
  50. data/lib/active_model/validations/length.rb +49 -18
  51. data/lib/active_model/validations/numericality.rb +20 -11
  52. data/lib/active_model/validations/validates.rb +1 -1
  53. data/lib/active_model/validations/with.rb +0 -10
  54. data/lib/active_model/validations.rb +34 -1
  55. data/lib/active_model/validator.rb +5 -5
  56. data/lib/active_model/version.rb +1 -1
  57. data/lib/active_model.rb +4 -2
  58. metadata +31 -22
  59. data/lib/active_model/serializers/xml.rb +0 -238
@@ -17,8 +17,9 @@ module ActiveModel
17
17
  module ForbiddenAttributesProtection # :nodoc:
18
18
  protected
19
19
  def sanitize_for_mass_assignment(attributes)
20
- if attributes.respond_to?(:permitted?) && !attributes.permitted?
21
- raise ActiveModel::ForbiddenAttributesError
20
+ if attributes.respond_to?(:permitted?)
21
+ raise ActiveModel::ForbiddenAttributesError if !attributes.permitted?
22
+ attributes.to_h
22
23
  else
23
24
  attributes
24
25
  end
@@ -1,14 +1,14 @@
1
1
  module ActiveModel
2
- # Returns the version of the currently loaded Active Model as a <tt>Gem::Version</tt>
2
+ # Returns the version of the currently loaded \Active \Model as a <tt>Gem::Version</tt>
3
3
  def self.gem_version
4
4
  Gem::Version.new VERSION::STRING
5
5
  end
6
6
 
7
7
  module VERSION
8
- MAJOR = 4
9
- MINOR = 2
10
- TINY = 11
11
- PRE = "3"
8
+ MAJOR = 5
9
+ MINOR = 0
10
+ TINY = 7
11
+ PRE = "2"
12
12
 
13
13
  STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
14
14
  end
@@ -21,28 +21,27 @@ module ActiveModel
21
21
  # +self+.
22
22
  module Tests
23
23
 
24
- # == Responds to <tt>to_key</tt>
24
+ # Passes if the object's model responds to <tt>to_key</tt> and if calling
25
+ # this method returns +nil+ when the object is not persisted.
26
+ # Fails otherwise.
25
27
  #
26
- # Returns an Enumerable of all (primary) key attributes
27
- # or nil if <tt>model.persisted?</tt> is false. This is used by
28
- # <tt>dom_id</tt> to generate unique ids for the object.
28
+ # <tt>to_key</tt> returns an Enumerable of all (primary) key attributes
29
+ # of the model, and is used to a generate unique DOM id for the object.
29
30
  def test_to_key
30
31
  assert model.respond_to?(:to_key), "The model should respond to to_key"
31
32
  def model.persisted?() false end
32
33
  assert model.to_key.nil?, "to_key should return nil when `persisted?` returns false"
33
34
  end
34
35
 
35
- # == Responds to <tt>to_param</tt>
36
- #
37
- # Returns a string representing the object's key suitable for use in URLs
38
- # or +nil+ if <tt>model.persisted?</tt> is +false+.
36
+ # Passes if the object's model responds to <tt>to_param</tt> and if
37
+ # calling this method returns +nil+ when the object is not persisted.
38
+ # Fails otherwise.
39
39
  #
40
+ # <tt>to_param</tt> is used to represent the object's key in URLs.
40
41
  # Implementers can decide to either raise an exception or provide a
41
42
  # default in case the record uses a composite primary key. There are no
42
43
  # tests for this behavior in lint because it doesn't make sense to force
43
44
  # any of the possible implementation strategies on the implementer.
44
- # However, if the resource is not persisted?, then <tt>to_param</tt>
45
- # should always return +nil+.
46
45
  def test_to_param
47
46
  assert model.respond_to?(:to_param), "The model should respond to to_param"
48
47
  def model.to_key() [1] end
@@ -50,32 +49,34 @@ module ActiveModel
50
49
  assert model.to_param.nil?, "to_param should return nil when `persisted?` returns false"
51
50
  end
52
51
 
53
- # == Responds to <tt>to_partial_path</tt>
52
+ # Passes if the object's model responds to <tt>to_partial_path</tt> and if
53
+ # calling this method returns a string. Fails otherwise.
54
54
  #
55
- # Returns a string giving a relative path. This is used for looking up
56
- # partials. For example, a BlogPost model might return "blog_posts/blog_post"
55
+ # <tt>to_partial_path</tt> is used for looking up partials. For example,
56
+ # a BlogPost model might return "blog_posts/blog_post".
57
57
  def test_to_partial_path
58
58
  assert model.respond_to?(:to_partial_path), "The model should respond to to_partial_path"
59
59
  assert_kind_of String, model.to_partial_path
60
60
  end
61
61
 
62
- # == Responds to <tt>persisted?</tt>
62
+ # Passes if the object's model responds to <tt>persisted?</tt> and if
63
+ # calling this method returns either +true+ or +false+. Fails otherwise.
63
64
  #
64
- # Returns a boolean that specifies whether the object has been persisted
65
- # yet. This is used when calculating the URL for an object. If the object
66
- # is not persisted, a form for that object, for instance, will route to
67
- # the create action. If it is persisted, a form for the object will routes
68
- # to the update action.
65
+ # <tt>persisted?</tt> is used when calculating the URL for an object.
66
+ # If the object is not persisted, a form for that object, for instance,
67
+ # will route to the create action. If it is persisted, a form for the
68
+ # object will route to the update action.
69
69
  def test_persisted?
70
70
  assert model.respond_to?(:persisted?), "The model should respond to persisted?"
71
71
  assert_boolean model.persisted?, "persisted?"
72
72
  end
73
73
 
74
- # == \Naming
74
+ # Passes if the object's model responds to <tt>model_name</tt> both as
75
+ # an instance method and as a class method, and if calling this method
76
+ # returns a string with some convenience methods: <tt>:human</tt>,
77
+ # <tt>:singular</tt> and <tt>:plural</tt>.
75
78
  #
76
- # Model.model_name and Model#model_name must return a string with some
77
- # convenience methods: # <tt>:human</tt>, <tt>:singular</tt> and
78
- # <tt>:plural</tt>. Check ActiveModel::Naming for more information.
79
+ # Check ActiveModel::Naming for more information.
79
80
  def test_model_naming
80
81
  assert model.class.respond_to?(:model_name), "The model class should respond to model_name"
81
82
  model_name = model.class.model_name
@@ -88,12 +89,15 @@ module ActiveModel
88
89
  assert_equal model.model_name, model.class.model_name
89
90
  end
90
91
 
91
- # == \Errors Testing
92
+ # Passes if the object's model responds to <tt>errors</tt> and if calling
93
+ # <tt>[](attribute)</tt> on the result of this method returns an array.
94
+ # Fails otherwise.
92
95
  #
93
- # Returns an object that implements [](attribute) defined which returns an
94
- # Array of Strings that are the errors for the attribute in question.
95
- # If localization is used, the Strings should be localized for the current
96
- # locale. If no error is present, this method should return an empty Array.
96
+ # <tt>errors[attribute]</tt> is used to retrieve the errors of a model
97
+ # for a given attribute. If errors are present, the method should return
98
+ # an array of strings that are the errors for the attribute in question.
99
+ # If localization is used, the strings should be localized for the current
100
+ # locale. If no error is present, the method should return an empty array.
97
101
  def test_errors_aref
98
102
  assert model.respond_to?(:errors), "The model should respond to errors"
99
103
  assert model.errors[:hello].is_a?(Array), "errors#[] should return an Array"
@@ -6,6 +6,7 @@ en:
6
6
  # The values :model, :attribute and :value are always available for interpolation
7
7
  # The value :count is available when applicable. Can be used for pluralization.
8
8
  messages:
9
+ model_invalid: "Validation failed: %{errors}"
9
10
  inclusion: "is not included in the list"
10
11
  exclusion: "is reserved"
11
12
  invalid: "is invalid"
@@ -16,7 +17,7 @@ en:
16
17
  present: "must be blank"
17
18
  too_long:
18
19
  one: "is too long (maximum is 1 character)"
19
- other: "is too long (maximum is %{count} characters)"
20
+ other: "is too long (maximum is %{count} characters)"
20
21
  too_short:
21
22
  one: "is too short (minimum is 1 character)"
22
23
  other: "is too short (minimum is %{count} characters)"
@@ -57,6 +57,7 @@ module ActiveModel
57
57
  # (see below).
58
58
  module Model
59
59
  extend ActiveSupport::Concern
60
+ include ActiveModel::AttributeAssignment
60
61
  include ActiveModel::Validations
61
62
  include ActiveModel::Conversion
62
63
 
@@ -75,10 +76,8 @@ module ActiveModel
75
76
  # person = Person.new(name: 'bob', age: '18')
76
77
  # person.name # => "bob"
77
78
  # person.age # => "18"
78
- def initialize(params={})
79
- params.each do |attr, value|
80
- self.public_send("#{attr}=", value)
81
- end if params
79
+ def initialize(attributes={})
80
+ assign_attributes(attributes) if attributes
82
81
 
83
82
  super()
84
83
  end
@@ -1,6 +1,7 @@
1
1
  require 'active_support/core_ext/hash/except'
2
2
  require 'active_support/core_ext/module/introspection'
3
3
  require 'active_support/core_ext/module/remove_method'
4
+ require 'active_support/core_ext/module/delegation'
4
5
 
5
6
  module ActiveModel
6
7
  class Name
@@ -163,7 +164,7 @@ module ActiveModel
163
164
  @route_key << "_index" if @plural == @singular
164
165
  end
165
166
 
166
- # Transform the model name into a more humane format, using I18n. By default,
167
+ # Transform the model name into a more human format, using I18n. By default,
167
168
  # it will underscore then humanize the class name.
168
169
  #
169
170
  # class BlogPost
@@ -190,8 +191,8 @@ module ActiveModel
190
191
 
191
192
  private
192
193
 
193
- def _singularize(string, replacement='_')
194
- ActiveSupport::Inflector.underscore(string).tr('/', replacement)
194
+ def _singularize(string)
195
+ ActiveSupport::Inflector.underscore(string).tr('/'.freeze, '_'.freeze)
195
196
  end
196
197
  end
197
198
 
@@ -212,7 +213,7 @@ module ActiveModel
212
213
  # BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
213
214
  #
214
215
  # Providing the functionality that ActiveModel::Naming provides in your object
215
- # is required to pass the Active Model Lint test. So either extending the
216
+ # is required to pass the \Active \Model Lint test. So either extending the
216
217
  # provided method below, or rolling your own is required.
217
218
  module Naming
218
219
  def self.extended(base) #:nodoc:
@@ -26,7 +26,7 @@ module ActiveModel
26
26
  # it). When this attribute has a +nil+ value, the validation will not be
27
27
  # triggered.
28
28
  #
29
- # For further customizability, it is possible to supress the default
29
+ # For further customizability, it is possible to suppress the default
30
30
  # validations by passing <tt>validations: false</tt> as an argument.
31
31
  #
32
32
  # Add bcrypt (~> 3.1.7) to Gemfile to use #has_secure_password:
@@ -77,13 +77,6 @@ module ActiveModel
77
77
  validates_length_of :password, maximum: ActiveModel::SecurePassword::MAX_PASSWORD_LENGTH_ALLOWED
78
78
  validates_confirmation_of :password, allow_blank: true
79
79
  end
80
-
81
- # This code is necessary as long as the protected_attributes gem is supported.
82
- if respond_to?(:attributes_protected_by_default)
83
- def self.attributes_protected_by_default #:nodoc:
84
- super + ['password_digest']
85
- end
86
- end
87
80
  end
88
81
  end
89
82
 
@@ -99,7 +92,7 @@ module ActiveModel
99
92
  # user.authenticate('notright') # => false
100
93
  # user.authenticate('mUc3m00RsqyRe') # => user
101
94
  def authenticate(unencrypted_password)
102
- BCrypt::Password.new(password_digest) == unencrypted_password && self
95
+ BCrypt::Password.new(password_digest).is_password?(unencrypted_password) && self
103
96
  end
104
97
 
105
98
  attr_reader :password
@@ -31,16 +31,14 @@ module ActiveModel
31
31
  # of the attributes hash's keys. In order to override this behavior, take a look
32
32
  # at the private method +read_attribute_for_serialization+.
33
33
  #
34
- # Most of the time though, either the JSON or XML serializations are needed.
35
- # Both of these modules automatically include the
36
- # <tt>ActiveModel::Serialization</tt> module, so there is no need to
37
- # explicitly include it.
34
+ # ActiveModel::Serializers::JSON module automatically includes
35
+ # the <tt>ActiveModel::Serialization</tt> module, so there is no need to
36
+ # explicitly include <tt>ActiveModel::Serialization</tt>.
38
37
  #
39
- # A minimal implementation including XML and JSON would be:
38
+ # A minimal implementation including JSON would be:
40
39
  #
41
40
  # class Person
42
41
  # include ActiveModel::Serializers::JSON
43
- # include ActiveModel::Serializers::Xml
44
42
  #
45
43
  # attr_accessor :name
46
44
  #
@@ -55,13 +53,11 @@ module ActiveModel
55
53
  # person.serializable_hash # => {"name"=>nil}
56
54
  # person.as_json # => {"name"=>nil}
57
55
  # person.to_json # => "{\"name\":null}"
58
- # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
59
56
  #
60
57
  # person.name = "Bob"
61
58
  # person.serializable_hash # => {"name"=>"Bob"}
62
59
  # person.as_json # => {"name"=>"Bob"}
63
60
  # person.to_json # => "{\"name\":\"Bob\"}"
64
- # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
65
61
  #
66
62
  # Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and
67
63
  # <tt>:include</tt>. The following are all valid examples:
@@ -94,6 +90,37 @@ module ActiveModel
94
90
  # person.serializable_hash(except: :name) # => {"age"=>22}
95
91
  # person.serializable_hash(methods: :capitalized_name)
96
92
  # # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"}
93
+ #
94
+ # Example with <tt>:include</tt> option
95
+ #
96
+ # class User
97
+ # include ActiveModel::Serializers::JSON
98
+ # attr_accessor :name, :notes # Emulate has_many :notes
99
+ # def attributes
100
+ # {'name' => nil}
101
+ # end
102
+ # end
103
+ #
104
+ # class Note
105
+ # include ActiveModel::Serializers::JSON
106
+ # attr_accessor :title, :text
107
+ # def attributes
108
+ # {'title' => nil, 'text' => nil}
109
+ # end
110
+ # end
111
+ #
112
+ # note = Note.new
113
+ # note.title = 'Battle of Austerlitz'
114
+ # note.text = 'Some text here'
115
+ #
116
+ # user = User.new
117
+ # user.name = 'Napoleon'
118
+ # user.notes = [note]
119
+ #
120
+ # user.serializable_hash
121
+ # # => {"name" => "Napoleon"}
122
+ # user.serializable_hash(include: { notes: { only: 'title' }})
123
+ # # => {"name" => "Napoleon", "notes" => [{"title"=>"Battle of Austerlitz"}]}
97
124
  def serializable_hash(options = nil)
98
125
  options ||= {}
99
126
 
@@ -107,7 +134,7 @@ module ActiveModel
107
134
  hash = {}
108
135
  attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
109
136
 
110
- Array(options[:methods]).each { |m| hash[m.to_s] = send(m) if respond_to?(m) }
137
+ Array(options[:methods]).each { |m| hash[m.to_s] = send(m) }
111
138
 
112
139
  serializable_add_includes(options) do |association, records, opts|
113
140
  hash[association.to_s] = if records.respond_to?(:to_ary)
@@ -0,0 +1,13 @@
1
+ require 'active_model/type/integer'
2
+
3
+ module ActiveModel
4
+ module Type
5
+ class BigInteger < Integer # :nodoc:
6
+ private
7
+
8
+ def max_value
9
+ ::Float::INFINITY
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,50 @@
1
+ module ActiveModel
2
+ module Type
3
+ class Binary < Value # :nodoc:
4
+ def type
5
+ :binary
6
+ end
7
+
8
+ def binary?
9
+ true
10
+ end
11
+
12
+ def cast(value)
13
+ if value.is_a?(Data)
14
+ value.to_s
15
+ else
16
+ super
17
+ end
18
+ end
19
+
20
+ def serialize(value)
21
+ return if value.nil?
22
+ Data.new(super)
23
+ end
24
+
25
+ def changed_in_place?(raw_old_value, value)
26
+ old_value = deserialize(raw_old_value)
27
+ old_value != value
28
+ end
29
+
30
+ class Data # :nodoc:
31
+ def initialize(value)
32
+ @value = value.to_s
33
+ end
34
+
35
+ def to_s
36
+ @value
37
+ end
38
+ alias_method :to_str, :to_s
39
+
40
+ def hex
41
+ @value.unpack('H*')[0]
42
+ end
43
+
44
+ def ==(other)
45
+ other == to_s || super
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,21 @@
1
+ module ActiveModel
2
+ module Type
3
+ class Boolean < Value # :nodoc:
4
+ FALSE_VALUES = [false, 0, '0', 'f', 'F', 'false', 'FALSE', 'off', 'OFF'].to_set
5
+
6
+ def type
7
+ :boolean
8
+ end
9
+
10
+ private
11
+
12
+ def cast_value(value)
13
+ if value == ''
14
+ nil
15
+ else
16
+ !FALSE_VALUES.include?(value)
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,54 @@
1
+ module ActiveModel
2
+ module Type
3
+ class Date < Value # :nodoc:
4
+ include Helpers::AcceptsMultiparameterTime.new
5
+
6
+ def type
7
+ :date
8
+ end
9
+
10
+ def serialize(value)
11
+ cast(value)
12
+ end
13
+
14
+ def type_cast_for_schema(value)
15
+ "'#{value.to_s(:db)}'"
16
+ end
17
+
18
+ private
19
+
20
+ def cast_value(value)
21
+ if value.is_a?(::String)
22
+ return if value.empty?
23
+ fast_string_to_date(value) || fallback_string_to_date(value)
24
+ elsif value.respond_to?(:to_date)
25
+ value.to_date
26
+ else
27
+ value
28
+ end
29
+ end
30
+
31
+ ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
32
+ def fast_string_to_date(string)
33
+ if string =~ ISO_DATE
34
+ new_date $1.to_i, $2.to_i, $3.to_i
35
+ end
36
+ end
37
+
38
+ def fallback_string_to_date(string)
39
+ new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
40
+ end
41
+
42
+ def new_date(year, mon, mday)
43
+ if year && year != 0
44
+ ::Date.new(year, mon, mday) rescue nil
45
+ end
46
+ end
47
+
48
+ def value_from_multiparameter_assignment(*)
49
+ time = super
50
+ time && time.to_date
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,44 @@
1
+ module ActiveModel
2
+ module Type
3
+ class DateTime < Value # :nodoc:
4
+ include Helpers::TimeValue
5
+ include Helpers::AcceptsMultiparameterTime.new(
6
+ defaults: { 4 => 0, 5 => 0 }
7
+ )
8
+
9
+ def type
10
+ :datetime
11
+ end
12
+
13
+ private
14
+
15
+ def cast_value(value)
16
+ return apply_seconds_precision(value) unless value.is_a?(::String)
17
+ return if value.empty?
18
+
19
+ fast_string_to_time(value) || fallback_string_to_time(value)
20
+ end
21
+
22
+ # '0.123456' -> 123456
23
+ # '1.123456' -> 123456
24
+ def microseconds(time)
25
+ time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
26
+ end
27
+
28
+ def fallback_string_to_time(string)
29
+ time_hash = ::Date._parse(string)
30
+ time_hash[:sec_fraction] = microseconds(time_hash)
31
+
32
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
33
+ end
34
+
35
+ def value_from_multiparameter_assignment(values_hash)
36
+ missing_parameter = (1..3).detect { |key| !values_hash.key?(key) }
37
+ if missing_parameter
38
+ raise ArgumentError, missing_parameter
39
+ end
40
+ super
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,66 @@
1
+ require "bigdecimal/util"
2
+
3
+ module ActiveModel
4
+ module Type
5
+ class Decimal < Value # :nodoc:
6
+ include Helpers::Numeric
7
+
8
+ def type
9
+ :decimal
10
+ end
11
+
12
+ def type_cast_for_schema(value)
13
+ value.to_s.inspect
14
+ end
15
+
16
+ private
17
+
18
+ def cast_value(value)
19
+ casted_value = case value
20
+ when ::Float
21
+ convert_float_to_big_decimal(value)
22
+ when ::Numeric
23
+ BigDecimal(value, precision.to_i)
24
+ when ::String
25
+ begin
26
+ value.to_d
27
+ rescue ArgumentError
28
+ BigDecimal(0)
29
+ end
30
+ else
31
+ if value.respond_to?(:to_d)
32
+ value.to_d
33
+ else
34
+ cast_value(value.to_s)
35
+ end
36
+ end
37
+
38
+ apply_scale(casted_value)
39
+ end
40
+
41
+ def convert_float_to_big_decimal(value)
42
+ if precision
43
+ BigDecimal(apply_scale(value), float_precision)
44
+ else
45
+ value.to_d
46
+ end
47
+ end
48
+
49
+ def float_precision
50
+ if precision.to_i > ::Float::DIG + 1
51
+ ::Float::DIG + 1
52
+ else
53
+ precision.to_i
54
+ end
55
+ end
56
+
57
+ def apply_scale(value)
58
+ if scale
59
+ value.round(scale)
60
+ else
61
+ value
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,11 @@
1
+ require 'active_model/type/big_integer'
2
+
3
+ module ActiveModel
4
+ module Type
5
+ class DecimalWithoutScale < BigInteger # :nodoc:
6
+ def type
7
+ :decimal
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ module ActiveModel
2
+ module Type
3
+ class Float < Value # :nodoc:
4
+ include Helpers::Numeric
5
+
6
+ def type
7
+ :float
8
+ end
9
+
10
+ alias serialize cast
11
+
12
+ private
13
+
14
+ def cast_value(value)
15
+ case value
16
+ when ::Float then value
17
+ when "Infinity" then ::Float::INFINITY
18
+ when "-Infinity" then -::Float::INFINITY
19
+ when "NaN" then ::Float::NAN
20
+ else value.to_f
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,35 @@
1
+ module ActiveModel
2
+ module Type
3
+ module Helpers
4
+ class AcceptsMultiparameterTime < Module # :nodoc:
5
+ def initialize(defaults: {})
6
+ define_method(:cast) do |value|
7
+ if value.is_a?(Hash)
8
+ value_from_multiparameter_assignment(value)
9
+ else
10
+ super(value)
11
+ end
12
+ end
13
+
14
+ define_method(:assert_valid_value) do |value|
15
+ if value.is_a?(Hash)
16
+ value_from_multiparameter_assignment(value)
17
+ else
18
+ super(value)
19
+ end
20
+ end
21
+
22
+ define_method(:value_from_multiparameter_assignment) do |values_hash|
23
+ defaults.each do |k, v|
24
+ values_hash[k] ||= v
25
+ end
26
+ return unless values_hash[1] && values_hash[2] && values_hash[3]
27
+ values = values_hash.sort.map(&:last)
28
+ ::Time.send(default_timezone, *values)
29
+ end
30
+ private :value_from_multiparameter_assignment
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end