activemodel 4.2.11.3 → 5.0.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of activemodel might be problematic. Click here for more details.
- checksums.yaml +5 -5
- data/CHANGELOG.md +84 -93
- data/MIT-LICENSE +1 -1
- data/README.rdoc +8 -16
- data/lib/active_model.rb +3 -2
- data/lib/active_model/attribute_assignment.rb +52 -0
- data/lib/active_model/attribute_methods.rb +16 -16
- data/lib/active_model/callbacks.rb +3 -3
- data/lib/active_model/conversion.rb +3 -3
- data/lib/active_model/dirty.rb +34 -35
- data/lib/active_model/errors.rb +117 -63
- data/lib/active_model/forbidden_attributes_protection.rb +3 -2
- data/lib/active_model/gem_version.rb +5 -5
- data/lib/active_model/lint.rb +32 -28
- data/lib/active_model/locale/en.yml +2 -1
- data/lib/active_model/model.rb +3 -4
- data/lib/active_model/naming.rb +5 -4
- data/lib/active_model/secure_password.rb +2 -9
- data/lib/active_model/serialization.rb +36 -9
- data/lib/active_model/serializers/json.rb +1 -1
- data/lib/active_model/type.rb +59 -0
- data/lib/active_model/type/big_integer.rb +13 -0
- data/lib/active_model/type/binary.rb +50 -0
- data/lib/active_model/type/boolean.rb +21 -0
- data/lib/active_model/type/date.rb +50 -0
- data/lib/active_model/type/date_time.rb +44 -0
- data/lib/active_model/type/decimal.rb +52 -0
- data/lib/active_model/type/decimal_without_scale.rb +11 -0
- data/lib/active_model/type/float.rb +25 -0
- data/lib/active_model/type/helpers.rb +4 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +35 -0
- data/lib/active_model/type/helpers/mutable.rb +18 -0
- data/lib/active_model/type/helpers/numeric.rb +34 -0
- data/lib/active_model/type/helpers/time_value.rb +77 -0
- data/lib/active_model/type/immutable_string.rb +29 -0
- data/lib/active_model/type/integer.rb +66 -0
- data/lib/active_model/type/registry.rb +64 -0
- data/lib/active_model/type/string.rb +19 -0
- data/lib/active_model/type/text.rb +11 -0
- data/lib/active_model/type/time.rb +46 -0
- data/lib/active_model/type/unsigned_integer.rb +15 -0
- data/lib/active_model/type/value.rb +112 -0
- data/lib/active_model/validations.rb +35 -3
- data/lib/active_model/validations/absence.rb +1 -1
- data/lib/active_model/validations/acceptance.rb +61 -9
- data/lib/active_model/validations/callbacks.rb +3 -3
- data/lib/active_model/validations/confirmation.rb +16 -4
- data/lib/active_model/validations/exclusion.rb +3 -1
- data/lib/active_model/validations/format.rb +1 -1
- data/lib/active_model/validations/helper_methods.rb +13 -0
- data/lib/active_model/validations/inclusion.rb +3 -3
- data/lib/active_model/validations/length.rb +48 -17
- data/lib/active_model/validations/numericality.rb +12 -13
- data/lib/active_model/validations/validates.rb +1 -1
- data/lib/active_model/validations/with.rb +0 -10
- data/lib/active_model/validator.rb +6 -2
- data/lib/active_model/version.rb +1 -1
- metadata +34 -9
- data/lib/active_model/serializers/xml.rb +0 -238
@@ -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,50 @@
|
|
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 type_cast_for_schema(value)
|
11
|
+
"'#{value.to_s(:db)}'"
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def cast_value(value)
|
17
|
+
if value.is_a?(::String)
|
18
|
+
return if value.empty?
|
19
|
+
fast_string_to_date(value) || fallback_string_to_date(value)
|
20
|
+
elsif value.respond_to?(:to_date)
|
21
|
+
value.to_date
|
22
|
+
else
|
23
|
+
value
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
ISO_DATE = /\A(\d{4})-(\d\d)-(\d\d)\z/
|
28
|
+
def fast_string_to_date(string)
|
29
|
+
if string =~ ISO_DATE
|
30
|
+
new_date $1.to_i, $2.to_i, $3.to_i
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def fallback_string_to_date(string)
|
35
|
+
new_date(*::Date._parse(string, false).values_at(:year, :mon, :mday))
|
36
|
+
end
|
37
|
+
|
38
|
+
def new_date(year, mon, mday)
|
39
|
+
if year && year != 0
|
40
|
+
::Date.new(year, mon, mday) rescue nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def value_from_multiparameter_assignment(*)
|
45
|
+
time = super
|
46
|
+
time && time.to_date
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
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,52 @@
|
|
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, ::String
|
23
|
+
BigDecimal(value, precision.to_i)
|
24
|
+
else
|
25
|
+
if value.respond_to?(:to_d)
|
26
|
+
value.to_d
|
27
|
+
else
|
28
|
+
cast_value(value.to_s)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
scale ? casted_value.round(scale) : casted_value
|
33
|
+
end
|
34
|
+
|
35
|
+
def convert_float_to_big_decimal(value)
|
36
|
+
if precision
|
37
|
+
BigDecimal(value, float_precision)
|
38
|
+
else
|
39
|
+
value.to_d
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def float_precision
|
44
|
+
if precision.to_i > ::Float::DIG + 1
|
45
|
+
::Float::DIG + 1
|
46
|
+
else
|
47
|
+
precision.to_i
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
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
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Type
|
3
|
+
module Helpers
|
4
|
+
module Mutable # :nodoc:
|
5
|
+
def cast(value)
|
6
|
+
deserialize(serialize(value))
|
7
|
+
end
|
8
|
+
|
9
|
+
# +raw_old_value+ will be the `_before_type_cast` version of the
|
10
|
+
# value (likely a string). +new_value+ will be the current, type
|
11
|
+
# cast value.
|
12
|
+
def changed_in_place?(raw_old_value, new_value)
|
13
|
+
raw_old_value != serialize(new_value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Type
|
3
|
+
module Helpers
|
4
|
+
module Numeric # :nodoc:
|
5
|
+
def cast(value)
|
6
|
+
value = case value
|
7
|
+
when true then 1
|
8
|
+
when false then 0
|
9
|
+
when ::String then value.presence
|
10
|
+
else value
|
11
|
+
end
|
12
|
+
super(value)
|
13
|
+
end
|
14
|
+
|
15
|
+
def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
|
16
|
+
super || number_to_non_number?(old_value, new_value_before_type_cast)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def number_to_non_number?(old_value, new_value_before_type_cast)
|
22
|
+
old_value != nil && non_numeric_string?(new_value_before_type_cast)
|
23
|
+
end
|
24
|
+
|
25
|
+
def non_numeric_string?(value)
|
26
|
+
# 'wibble'.to_i will give zero, we want to make sure
|
27
|
+
# that we aren't marking int zero to string zero as
|
28
|
+
# changed.
|
29
|
+
value.to_s !~ /\A-?\d+\.?\d*\z/
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require "active_support/core_ext/time/zones"
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Type
|
5
|
+
module Helpers
|
6
|
+
module TimeValue # :nodoc:
|
7
|
+
def serialize(value)
|
8
|
+
value = apply_seconds_precision(value)
|
9
|
+
|
10
|
+
if value.acts_like?(:time)
|
11
|
+
zone_conversion_method = is_utc? ? :getutc : :getlocal
|
12
|
+
|
13
|
+
if value.respond_to?(zone_conversion_method)
|
14
|
+
value = value.send(zone_conversion_method)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
value
|
19
|
+
end
|
20
|
+
|
21
|
+
def is_utc?
|
22
|
+
::Time.zone_default.nil? || ::Time.zone_default =~ 'UTC'
|
23
|
+
end
|
24
|
+
|
25
|
+
def default_timezone
|
26
|
+
if is_utc?
|
27
|
+
:utc
|
28
|
+
else
|
29
|
+
:local
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def apply_seconds_precision(value)
|
34
|
+
return value unless precision && value.respond_to?(:usec)
|
35
|
+
number_of_insignificant_digits = 6 - precision
|
36
|
+
round_power = 10 ** number_of_insignificant_digits
|
37
|
+
value.change(usec: value.usec / round_power * round_power)
|
38
|
+
end
|
39
|
+
|
40
|
+
def type_cast_for_schema(value)
|
41
|
+
"'#{value.to_s(:db)}'"
|
42
|
+
end
|
43
|
+
|
44
|
+
def user_input_in_time_zone(value)
|
45
|
+
value.in_time_zone
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
|
51
|
+
# Treat 0000-00-00 00:00:00 as nil.
|
52
|
+
return if year.nil? || (year == 0 && mon == 0 && mday == 0)
|
53
|
+
|
54
|
+
if offset
|
55
|
+
time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
|
56
|
+
return unless time
|
57
|
+
|
58
|
+
time -= offset
|
59
|
+
is_utc? ? time : time.getlocal
|
60
|
+
else
|
61
|
+
::Time.public_send(default_timezone, year, mon, mday, hour, min, sec, microsec) rescue nil
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
ISO_DATETIME = /\A(\d{4})-(\d\d)-(\d\d) (\d\d):(\d\d):(\d\d)(\.\d+)?\z/
|
66
|
+
|
67
|
+
# Doesn't handle time zones.
|
68
|
+
def fast_string_to_time(string)
|
69
|
+
if string =~ ISO_DATETIME
|
70
|
+
microsec = ($7.to_r * 1_000_000).to_i
|
71
|
+
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Type
|
3
|
+
class ImmutableString < Value # :nodoc:
|
4
|
+
def type
|
5
|
+
:string
|
6
|
+
end
|
7
|
+
|
8
|
+
def serialize(value)
|
9
|
+
case value
|
10
|
+
when ::Numeric, ActiveSupport::Duration then value.to_s
|
11
|
+
when true then "t"
|
12
|
+
when false then "f"
|
13
|
+
else super
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def cast_value(value)
|
20
|
+
result = case value
|
21
|
+
when true then "t"
|
22
|
+
when false then "f"
|
23
|
+
else value.to_s
|
24
|
+
end
|
25
|
+
result.freeze
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Type
|
3
|
+
class Integer < Value # :nodoc:
|
4
|
+
include Helpers::Numeric
|
5
|
+
|
6
|
+
# Column storage size in bytes.
|
7
|
+
# 4 bytes means a MySQL int or Postgres integer as opposed to smallint etc.
|
8
|
+
DEFAULT_LIMIT = 4
|
9
|
+
|
10
|
+
def initialize(*)
|
11
|
+
super
|
12
|
+
@range = min_value...max_value
|
13
|
+
end
|
14
|
+
|
15
|
+
def type
|
16
|
+
:integer
|
17
|
+
end
|
18
|
+
|
19
|
+
def deserialize(value)
|
20
|
+
return if value.nil?
|
21
|
+
value.to_i
|
22
|
+
end
|
23
|
+
|
24
|
+
def serialize(value)
|
25
|
+
result = cast(value)
|
26
|
+
if result
|
27
|
+
ensure_in_range(result)
|
28
|
+
end
|
29
|
+
result
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
attr_reader :range
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def cast_value(value)
|
39
|
+
case value
|
40
|
+
when true then 1
|
41
|
+
when false then 0
|
42
|
+
else
|
43
|
+
value.to_i rescue nil
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def ensure_in_range(value)
|
48
|
+
unless range.cover?(value)
|
49
|
+
raise RangeError, "#{value} is out of range for #{self.class} with limit #{_limit}"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def max_value
|
54
|
+
1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign
|
55
|
+
end
|
56
|
+
|
57
|
+
def min_value
|
58
|
+
-max_value
|
59
|
+
end
|
60
|
+
|
61
|
+
def _limit
|
62
|
+
self.limit || DEFAULT_LIMIT
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|