activemodel 4.2.0 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +49 -37
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +16 -22
  5. data/lib/active_model/attribute/user_provided_default.rb +51 -0
  6. data/lib/active_model/attribute.rb +248 -0
  7. data/lib/active_model/attribute_assignment.rb +55 -0
  8. data/lib/active_model/attribute_methods.rb +150 -73
  9. data/lib/active_model/attribute_mutation_tracker.rb +181 -0
  10. data/lib/active_model/attribute_set/builder.rb +191 -0
  11. data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
  12. data/lib/active_model/attribute_set.rb +106 -0
  13. data/lib/active_model/attributes.rb +132 -0
  14. data/lib/active_model/callbacks.rb +31 -25
  15. data/lib/active_model/conversion.rb +20 -9
  16. data/lib/active_model/dirty.rb +142 -116
  17. data/lib/active_model/error.rb +207 -0
  18. data/lib/active_model/errors.rb +436 -202
  19. data/lib/active_model/forbidden_attributes_protection.rb +6 -3
  20. data/lib/active_model/gem_version.rb +5 -3
  21. data/lib/active_model/lint.rb +47 -42
  22. data/lib/active_model/locale/en.yml +2 -1
  23. data/lib/active_model/model.rb +7 -7
  24. data/lib/active_model/naming.rb +36 -18
  25. data/lib/active_model/nested_error.rb +22 -0
  26. data/lib/active_model/railtie.rb +8 -0
  27. data/lib/active_model/secure_password.rb +61 -67
  28. data/lib/active_model/serialization.rb +48 -17
  29. data/lib/active_model/serializers/json.rb +22 -13
  30. data/lib/active_model/translation.rb +5 -4
  31. data/lib/active_model/type/big_integer.rb +14 -0
  32. data/lib/active_model/type/binary.rb +52 -0
  33. data/lib/active_model/type/boolean.rb +46 -0
  34. data/lib/active_model/type/date.rb +52 -0
  35. data/lib/active_model/type/date_time.rb +46 -0
  36. data/lib/active_model/type/decimal.rb +69 -0
  37. data/lib/active_model/type/float.rb +35 -0
  38. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +49 -0
  39. data/lib/active_model/type/helpers/mutable.rb +20 -0
  40. data/lib/active_model/type/helpers/numeric.rb +48 -0
  41. data/lib/active_model/type/helpers/time_value.rb +90 -0
  42. data/lib/active_model/type/helpers/timezone.rb +19 -0
  43. data/lib/active_model/type/helpers.rb +7 -0
  44. data/lib/active_model/type/immutable_string.rb +35 -0
  45. data/lib/active_model/type/integer.rb +67 -0
  46. data/lib/active_model/type/registry.rb +70 -0
  47. data/lib/active_model/type/string.rb +35 -0
  48. data/lib/active_model/type/time.rb +46 -0
  49. data/lib/active_model/type/value.rb +133 -0
  50. data/lib/active_model/type.rb +53 -0
  51. data/lib/active_model/validations/absence.rb +6 -4
  52. data/lib/active_model/validations/acceptance.rb +72 -14
  53. data/lib/active_model/validations/callbacks.rb +23 -19
  54. data/lib/active_model/validations/clusivity.rb +18 -12
  55. data/lib/active_model/validations/confirmation.rb +27 -14
  56. data/lib/active_model/validations/exclusion.rb +7 -4
  57. data/lib/active_model/validations/format.rb +27 -27
  58. data/lib/active_model/validations/helper_methods.rb +15 -0
  59. data/lib/active_model/validations/inclusion.rb +8 -7
  60. data/lib/active_model/validations/length.rb +35 -32
  61. data/lib/active_model/validations/numericality.rb +72 -34
  62. data/lib/active_model/validations/presence.rb +3 -3
  63. data/lib/active_model/validations/validates.rb +17 -15
  64. data/lib/active_model/validations/with.rb +6 -12
  65. data/lib/active_model/validations.rb +58 -23
  66. data/lib/active_model/validator.rb +23 -17
  67. data/lib/active_model/version.rb +4 -2
  68. data/lib/active_model.rb +18 -11
  69. metadata +44 -25
  70. data/lib/active_model/serializers/xml.rb +0 -238
  71. data/lib/active_model/test_case.rb +0 -4
@@ -1,5 +1,6 @@
1
- require 'active_support/core_ext/hash/except'
2
- require 'active_support/core_ext/hash/slice'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/enumerable"
3
4
 
4
5
  module ActiveModel
5
6
  # == Active \Model \Serialization
@@ -31,16 +32,14 @@ module ActiveModel
31
32
  # of the attributes hash's keys. In order to override this behavior, take a look
32
33
  # at the private method +read_attribute_for_serialization+.
33
34
  #
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.
35
+ # ActiveModel::Serializers::JSON module automatically includes
36
+ # the <tt>ActiveModel::Serialization</tt> module, so there is no need to
37
+ # explicitly include <tt>ActiveModel::Serialization</tt>.
38
38
  #
39
- # A minimal implementation including XML and JSON would be:
39
+ # A minimal implementation including JSON would be:
40
40
  #
41
41
  # class Person
42
42
  # include ActiveModel::Serializers::JSON
43
- # include ActiveModel::Serializers::Xml
44
43
  #
45
44
  # attr_accessor :name
46
45
  #
@@ -55,13 +54,11 @@ module ActiveModel
55
54
  # person.serializable_hash # => {"name"=>nil}
56
55
  # person.as_json # => {"name"=>nil}
57
56
  # person.to_json # => "{\"name\":null}"
58
- # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
59
57
  #
60
58
  # person.name = "Bob"
61
59
  # person.serializable_hash # => {"name"=>"Bob"}
62
60
  # person.as_json # => {"name"=>"Bob"}
63
61
  # person.to_json # => "{\"name\":\"Bob\"}"
64
- # person.to_xml # => "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<serial-person...
65
62
  #
66
63
  # Valid options are <tt>:only</tt>, <tt>:except</tt>, <tt>:methods</tt> and
67
64
  # <tt>:include</tt>. The following are all valid examples:
@@ -94,20 +91,51 @@ module ActiveModel
94
91
  # person.serializable_hash(except: :name) # => {"age"=>22}
95
92
  # person.serializable_hash(methods: :capitalized_name)
96
93
  # # => {"name"=>"bob", "age"=>22, "capitalized_name"=>"Bob"}
94
+ #
95
+ # Example with <tt>:include</tt> option
96
+ #
97
+ # class User
98
+ # include ActiveModel::Serializers::JSON
99
+ # attr_accessor :name, :notes # Emulate has_many :notes
100
+ # def attributes
101
+ # {'name' => nil}
102
+ # end
103
+ # end
104
+ #
105
+ # class Note
106
+ # include ActiveModel::Serializers::JSON
107
+ # attr_accessor :title, :text
108
+ # def attributes
109
+ # {'title' => nil, 'text' => nil}
110
+ # end
111
+ # end
112
+ #
113
+ # note = Note.new
114
+ # note.title = 'Battle of Austerlitz'
115
+ # note.text = 'Some text here'
116
+ #
117
+ # user = User.new
118
+ # user.name = 'Napoleon'
119
+ # user.notes = [note]
120
+ #
121
+ # user.serializable_hash
122
+ # # => {"name" => "Napoleon"}
123
+ # user.serializable_hash(include: { notes: { only: 'title' }})
124
+ # # => {"name" => "Napoleon", "notes" => [{"title"=>"Battle of Austerlitz"}]}
97
125
  def serializable_hash(options = nil)
98
- options ||= {}
99
-
100
126
  attribute_names = attributes.keys
127
+
128
+ return serializable_attributes(attribute_names) if options.blank?
129
+
101
130
  if only = options[:only]
102
131
  attribute_names &= Array(only).map(&:to_s)
103
132
  elsif except = options[:except]
104
133
  attribute_names -= Array(except).map(&:to_s)
105
134
  end
106
135
 
107
- hash = {}
108
- attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
136
+ hash = serializable_attributes(attribute_names)
109
137
 
110
- Array(options[:methods]).each { |m| hash[m.to_s] = send(m) if respond_to?(m) }
138
+ Array(options[:methods]).each { |m| hash[m.to_s] = send(m) }
111
139
 
112
140
  serializable_add_includes(options) do |association, records, opts|
113
141
  hash[association.to_s] = if records.respond_to?(:to_ary)
@@ -121,7 +149,6 @@ module ActiveModel
121
149
  end
122
150
 
123
151
  private
124
-
125
152
  # Hook method defining how an attribute value should be retrieved for
126
153
  # serialization. By default this is assumed to be an instance named after
127
154
  # the attribute. Override this method in subclasses should you need to
@@ -140,6 +167,10 @@ module ActiveModel
140
167
  # end
141
168
  alias :read_attribute_for_serialization :send
142
169
 
170
+ def serializable_attributes(attribute_names)
171
+ attribute_names.index_with { |n| read_attribute_for_serialization(n) }
172
+ end
173
+
143
174
  # Add associations specified via the <tt>:include</tt> option.
144
175
  #
145
176
  # Expects a block that takes as arguments:
@@ -150,7 +181,7 @@ module ActiveModel
150
181
  return unless includes = options[:include]
151
182
 
152
183
  unless includes.is_a?(Hash)
153
- includes = Hash[Array(includes).map { |n| n.is_a?(Hash) ? n.to_a.first : [n, {}] }]
184
+ includes = Hash[Array(includes).flat_map { |n| n.is_a?(Hash) ? n.to_a : [[n, {}]] }]
154
185
  end
155
186
 
156
187
  includes.each do |association, opts|
@@ -1,4 +1,6 @@
1
- require 'active_support/json'
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/json"
2
4
 
3
5
  module ActiveModel
4
6
  module Serializers
@@ -10,8 +12,7 @@ module ActiveModel
10
12
  included do
11
13
  extend ActiveModel::Naming
12
14
 
13
- class_attribute :include_root_in_json
14
- self.include_root_in_json = false
15
+ class_attribute :include_root_in_json, instance_writer: false, default: false
15
16
  end
16
17
 
17
18
  # Returns a hash representing the model. Some configuration can be
@@ -25,13 +26,13 @@ module ActiveModel
25
26
  # user = User.find(1)
26
27
  # user.as_json
27
28
  # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
28
- # # "created_at" => "2006/08/01", "awesome" => true}
29
+ # # "created_at" => "2006-08-01T17:27:133.000Z", "awesome" => true}
29
30
  #
30
31
  # ActiveRecord::Base.include_root_in_json = true
31
32
  #
32
33
  # user.as_json
33
34
  # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
34
- # # "created_at" => "2006/08/01", "awesome" => true } }
35
+ # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }
35
36
  #
36
37
  # This behavior can also be achieved by setting the <tt>:root</tt> option
37
38
  # to +true+ as in:
@@ -39,7 +40,14 @@ module ActiveModel
39
40
  # user = User.find(1)
40
41
  # user.as_json(root: true)
41
42
  # # => { "user" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
42
- # # "created_at" => "2006/08/01", "awesome" => true } }
43
+ # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }
44
+ #
45
+ # If you prefer, <tt>:root</tt> may also be set to a custom string key instead as in:
46
+ #
47
+ # user = User.find(1)
48
+ # user.as_json(root: "author")
49
+ # # => { "author" => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
50
+ # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true } }
43
51
  #
44
52
  # Without any +options+, the returned Hash will include all the model's
45
53
  # attributes.
@@ -47,7 +55,7 @@ module ActiveModel
47
55
  # user = User.find(1)
48
56
  # user.as_json
49
57
  # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
50
- # # "created_at" => "2006/08/01", "awesome" => true}
58
+ # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true}
51
59
  #
52
60
  # The <tt>:only</tt> and <tt>:except</tt> options can be used to limit
53
61
  # the attributes included, and work similar to the +attributes+ method.
@@ -62,14 +70,14 @@ module ActiveModel
62
70
  #
63
71
  # user.as_json(methods: :permalink)
64
72
  # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
65
- # # "created_at" => "2006/08/01", "awesome" => true,
73
+ # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
66
74
  # # "permalink" => "1-konata-izumi" }
67
75
  #
68
76
  # To include associations use <tt>:include</tt>:
69
77
  #
70
78
  # user.as_json(include: :posts)
71
79
  # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
72
- # # "created_at" => "2006/08/01", "awesome" => true,
80
+ # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
73
81
  # # "posts" => [ { "id" => 1, "author_id" => 1, "title" => "Welcome to the weblog" },
74
82
  # # { "id" => 2, "author_id" => 1, "title" => "So I was thinking" } ] }
75
83
  #
@@ -80,7 +88,7 @@ module ActiveModel
80
88
  # only: :body } },
81
89
  # only: :title } })
82
90
  # # => { "id" => 1, "name" => "Konata Izumi", "age" => 16,
83
- # # "created_at" => "2006/08/01", "awesome" => true,
91
+ # # "created_at" => "2006-08-01T17:27:13.000Z", "awesome" => true,
84
92
  # # "posts" => [ { "comments" => [ { "body" => "1st post!" }, { "body" => "Second!" } ],
85
93
  # # "title" => "Welcome to the weblog" },
86
94
  # # { "comments" => [ { "body" => "Don't think too hard" } ],
@@ -92,11 +100,12 @@ module ActiveModel
92
100
  include_root_in_json
93
101
  end
94
102
 
103
+ hash = serializable_hash(options).as_json
95
104
  if root
96
105
  root = model_name.element if root == true
97
- { root => serializable_hash(options) }
106
+ { root => hash }
98
107
  else
99
- serializable_hash(options)
108
+ hash
100
109
  end
101
110
  end
102
111
 
@@ -134,7 +143,7 @@ module ActiveModel
134
143
  # person.name # => "bob"
135
144
  # person.age # => 22
136
145
  # person.awesome # => true
137
- def from_json(json, include_root=include_root_in_json)
146
+ def from_json(json, include_root = include_root_in_json)
138
147
  hash = ActiveSupport::JSON.decode(json)
139
148
  hash = hash.values.first if include_root
140
149
  self.attributes = hash
@@ -1,5 +1,6 @@
1
- module ActiveModel
1
+ # frozen_string_literal: true
2
2
 
3
+ module ActiveModel
3
4
  # == Active \Model \Translation
4
5
  #
5
6
  # Provides integration between your object and the Rails internationalization
@@ -31,7 +32,7 @@ module ActiveModel
31
32
  # ActiveModel::Errors#full_messages and
32
33
  # ActiveModel::Translation#human_attribute_name.
33
34
  def lookup_ancestors
34
- self.ancestors.select { |x| x.respond_to?(:model_name) }
35
+ ancestors.select { |x| x.respond_to?(:model_name) }
35
36
  end
36
37
 
37
38
  # Transforms attribute names into a more human format, such as "First name"
@@ -45,7 +46,7 @@ module ActiveModel
45
46
  parts = attribute.to_s.split(".")
46
47
  attribute = parts.pop
47
48
  namespace = parts.join("/") unless parts.empty?
48
- attributes_scope = "#{self.i18n_scope}.attributes"
49
+ attributes_scope = "#{i18n_scope}.attributes"
49
50
 
50
51
  if namespace
51
52
  defaults = lookup_ancestors.map do |klass|
@@ -63,7 +64,7 @@ module ActiveModel
63
64
  defaults << attribute.humanize
64
65
 
65
66
  options[:default] = defaults
66
- I18n.translate(defaults.shift, options)
67
+ I18n.translate(defaults.shift, **options)
67
68
  end
68
69
  end
69
70
  end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/type/integer"
4
+
5
+ module ActiveModel
6
+ module Type
7
+ class BigInteger < Integer # :nodoc:
8
+ private
9
+ def max_value
10
+ ::Float::INFINITY
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ class Binary < Value # :nodoc:
6
+ def type
7
+ :binary
8
+ end
9
+
10
+ def binary?
11
+ true
12
+ end
13
+
14
+ def cast(value)
15
+ if value.is_a?(Data)
16
+ value.to_s
17
+ else
18
+ super
19
+ end
20
+ end
21
+
22
+ def serialize(value)
23
+ return if value.nil?
24
+ Data.new(super)
25
+ end
26
+
27
+ def changed_in_place?(raw_old_value, value)
28
+ old_value = deserialize(raw_old_value)
29
+ old_value != value
30
+ end
31
+
32
+ class Data # :nodoc:
33
+ def initialize(value)
34
+ @value = value.to_s
35
+ end
36
+
37
+ def to_s
38
+ @value
39
+ end
40
+ alias_method :to_str, :to_s
41
+
42
+ def hex
43
+ @value.unpack1("H*")
44
+ end
45
+
46
+ def ==(other)
47
+ other == to_s || super
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ # == Active \Model \Type \Boolean
6
+ #
7
+ # A class that behaves like a boolean type, including rules for coercion of user input.
8
+ #
9
+ # === Coercion
10
+ # Values set from user input will first be coerced into the appropriate ruby type.
11
+ # Coercion behavior is roughly mapped to Ruby's boolean semantics.
12
+ #
13
+ # - "false", "f" , "0", +0+ or any other value in +FALSE_VALUES+ will be coerced to +false+
14
+ # - Empty strings are coerced to +nil+
15
+ # - All other values will be coerced to +true+
16
+ class Boolean < Value
17
+ FALSE_VALUES = [
18
+ false, 0,
19
+ "0", :"0",
20
+ "f", :f,
21
+ "F", :F,
22
+ "false", :false,
23
+ "FALSE", :FALSE,
24
+ "off", :off,
25
+ "OFF", :OFF,
26
+ ].to_set.freeze
27
+
28
+ def type # :nodoc:
29
+ :boolean
30
+ end
31
+
32
+ def serialize(value) # :nodoc:
33
+ cast(value)
34
+ end
35
+
36
+ private
37
+ def cast_value(value)
38
+ if value == ""
39
+ nil
40
+ else
41
+ !FALSE_VALUES.include?(value)
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ class Date < Value # :nodoc:
6
+ include Helpers::Timezone
7
+ include Helpers::AcceptsMultiparameterTime.new
8
+
9
+ def type
10
+ :date
11
+ end
12
+
13
+ def type_cast_for_schema(value)
14
+ value.to_s(:db).inspect
15
+ end
16
+
17
+ private
18
+ def cast_value(value)
19
+ if value.is_a?(::String)
20
+ return if value.empty?
21
+ fast_string_to_date(value) || fallback_string_to_date(value)
22
+ elsif value.respond_to?(:to_date)
23
+ value.to_date
24
+ else
25
+ value
26
+ end
27
+ end
28
+
29
+ ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
30
+ def fast_string_to_date(string)
31
+ if string =~ ISO_DATE
32
+ new_date $1.to_i, $2.to_i, $3.to_i
33
+ end
34
+ end
35
+
36
+ def fallback_string_to_date(string)
37
+ new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
38
+ end
39
+
40
+ def new_date(year, mon, mday)
41
+ unless year.nil? || (year == 0 && mon == 0 && mday == 0)
42
+ ::Date.new(year, mon, mday) rescue nil
43
+ end
44
+ end
45
+
46
+ def value_from_multiparameter_assignment(*)
47
+ time = super
48
+ time && new_date(time.year, time.mon, time.mday)
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ class DateTime < Value # :nodoc:
6
+ include Helpers::Timezone
7
+ include Helpers::TimeValue
8
+ include Helpers::AcceptsMultiparameterTime.new(
9
+ defaults: { 4 => 0, 5 => 0 }
10
+ )
11
+
12
+ def type
13
+ :datetime
14
+ end
15
+
16
+ private
17
+ def cast_value(value)
18
+ return apply_seconds_precision(value) unless value.is_a?(::String)
19
+ return if value.empty?
20
+
21
+ fast_string_to_time(value) || fallback_string_to_time(value)
22
+ end
23
+
24
+ # '0.123456' -> 123456
25
+ # '1.123456' -> 123456
26
+ def microseconds(time)
27
+ time[:sec_fraction] ? (time[:sec_fraction] * 1_000_000).to_i : 0
28
+ end
29
+
30
+ def fallback_string_to_time(string)
31
+ time_hash = ::Date._parse(string)
32
+ time_hash[:sec_fraction] = microseconds(time_hash)
33
+
34
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
35
+ end
36
+
37
+ def value_from_multiparameter_assignment(values_hash)
38
+ missing_parameters = [1, 2, 3].delete_if { |key| values_hash.key?(key) }
39
+ unless missing_parameters.empty?
40
+ raise ArgumentError, "Provided hash #{values_hash} doesn't contain necessary keys: #{missing_parameters}"
41
+ end
42
+ super
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bigdecimal/util"
4
+
5
+ module ActiveModel
6
+ module Type
7
+ class Decimal < Value # :nodoc:
8
+ include Helpers::Numeric
9
+ BIGDECIMAL_PRECISION = 18
10
+
11
+ def type
12
+ :decimal
13
+ end
14
+
15
+ def type_cast_for_schema(value)
16
+ value.to_s.inspect
17
+ end
18
+
19
+ private
20
+ def cast_value(value)
21
+ casted_value = \
22
+ case value
23
+ when ::Float
24
+ convert_float_to_big_decimal(value)
25
+ when ::Numeric
26
+ BigDecimal(value, precision || BIGDECIMAL_PRECISION)
27
+ when ::String
28
+ begin
29
+ value.to_d
30
+ rescue ArgumentError
31
+ BigDecimal(0)
32
+ end
33
+ else
34
+ if value.respond_to?(:to_d)
35
+ value.to_d
36
+ else
37
+ cast_value(value.to_s)
38
+ end
39
+ end
40
+
41
+ apply_scale(casted_value)
42
+ end
43
+
44
+ def convert_float_to_big_decimal(value)
45
+ if precision
46
+ BigDecimal(apply_scale(value), float_precision)
47
+ else
48
+ value.to_d
49
+ end
50
+ end
51
+
52
+ def float_precision
53
+ if precision.to_i > ::Float::DIG + 1
54
+ ::Float::DIG + 1
55
+ else
56
+ precision.to_i
57
+ end
58
+ end
59
+
60
+ def apply_scale(value)
61
+ if scale
62
+ value.round(scale)
63
+ else
64
+ value
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/object/try"
4
+
5
+ module ActiveModel
6
+ module Type
7
+ class Float < Value # :nodoc:
8
+ include Helpers::Numeric
9
+
10
+ def type
11
+ :float
12
+ end
13
+
14
+ def type_cast_for_schema(value)
15
+ return "::Float::NAN" if value.try(:nan?)
16
+ case value
17
+ when ::Float::INFINITY then "::Float::INFINITY"
18
+ when -::Float::INFINITY then "-::Float::INFINITY"
19
+ else super
20
+ end
21
+ end
22
+
23
+ private
24
+ def cast_value(value)
25
+ case value
26
+ when ::Float then value
27
+ when "Infinity" then ::Float::INFINITY
28
+ when "-Infinity" then -::Float::INFINITY
29
+ when "NaN" then ::Float::NAN
30
+ else value.to_f
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ module Helpers # :nodoc: all
6
+ class AcceptsMultiparameterTime < Module
7
+ module InstanceMethods
8
+ def serialize(value)
9
+ super(cast(value))
10
+ end
11
+
12
+ def cast(value)
13
+ if value.is_a?(Hash)
14
+ value_from_multiparameter_assignment(value)
15
+ else
16
+ super(value)
17
+ end
18
+ end
19
+
20
+ def assert_valid_value(value)
21
+ if value.is_a?(Hash)
22
+ value_from_multiparameter_assignment(value)
23
+ else
24
+ super(value)
25
+ end
26
+ end
27
+
28
+ def value_constructed_by_mass_assignment?(value)
29
+ value.is_a?(Hash)
30
+ end
31
+ end
32
+
33
+ def initialize(defaults: {})
34
+ include InstanceMethods
35
+
36
+ define_method(:value_from_multiparameter_assignment) do |values_hash|
37
+ defaults.each do |k, v|
38
+ values_hash[k] ||= v
39
+ end
40
+ return unless values_hash[1] && values_hash[2] && values_hash[3]
41
+ values = values_hash.sort.map!(&:last)
42
+ ::Time.public_send(default_timezone, *values)
43
+ end
44
+ private :value_from_multiparameter_assignment
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ module Helpers # :nodoc: all
6
+ module Mutable
7
+ def cast(value)
8
+ deserialize(serialize(value))
9
+ end
10
+
11
+ # +raw_old_value+ will be the `_before_type_cast` version of the
12
+ # value (likely a string). +new_value+ will be the current, type
13
+ # cast value.
14
+ def changed_in_place?(raw_old_value, new_value)
15
+ raw_old_value != serialize(new_value)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end