activemodel 4.2.11.3 → 5.0.7.2

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