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.

Files changed (59) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +84 -93
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +8 -16
  5. data/lib/active_model.rb +3 -2
  6. data/lib/active_model/attribute_assignment.rb +52 -0
  7. data/lib/active_model/attribute_methods.rb +16 -16
  8. data/lib/active_model/callbacks.rb +3 -3
  9. data/lib/active_model/conversion.rb +3 -3
  10. data/lib/active_model/dirty.rb +34 -35
  11. data/lib/active_model/errors.rb +117 -63
  12. data/lib/active_model/forbidden_attributes_protection.rb +3 -2
  13. data/lib/active_model/gem_version.rb +5 -5
  14. data/lib/active_model/lint.rb +32 -28
  15. data/lib/active_model/locale/en.yml +2 -1
  16. data/lib/active_model/model.rb +3 -4
  17. data/lib/active_model/naming.rb +5 -4
  18. data/lib/active_model/secure_password.rb +2 -9
  19. data/lib/active_model/serialization.rb +36 -9
  20. data/lib/active_model/serializers/json.rb +1 -1
  21. data/lib/active_model/type.rb +59 -0
  22. data/lib/active_model/type/big_integer.rb +13 -0
  23. data/lib/active_model/type/binary.rb +50 -0
  24. data/lib/active_model/type/boolean.rb +21 -0
  25. data/lib/active_model/type/date.rb +50 -0
  26. data/lib/active_model/type/date_time.rb +44 -0
  27. data/lib/active_model/type/decimal.rb +52 -0
  28. data/lib/active_model/type/decimal_without_scale.rb +11 -0
  29. data/lib/active_model/type/float.rb +25 -0
  30. data/lib/active_model/type/helpers.rb +4 -0
  31. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +35 -0
  32. data/lib/active_model/type/helpers/mutable.rb +18 -0
  33. data/lib/active_model/type/helpers/numeric.rb +34 -0
  34. data/lib/active_model/type/helpers/time_value.rb +77 -0
  35. data/lib/active_model/type/immutable_string.rb +29 -0
  36. data/lib/active_model/type/integer.rb +66 -0
  37. data/lib/active_model/type/registry.rb +64 -0
  38. data/lib/active_model/type/string.rb +19 -0
  39. data/lib/active_model/type/text.rb +11 -0
  40. data/lib/active_model/type/time.rb +46 -0
  41. data/lib/active_model/type/unsigned_integer.rb +15 -0
  42. data/lib/active_model/type/value.rb +112 -0
  43. data/lib/active_model/validations.rb +35 -3
  44. data/lib/active_model/validations/absence.rb +1 -1
  45. data/lib/active_model/validations/acceptance.rb +61 -9
  46. data/lib/active_model/validations/callbacks.rb +3 -3
  47. data/lib/active_model/validations/confirmation.rb +16 -4
  48. data/lib/active_model/validations/exclusion.rb +3 -1
  49. data/lib/active_model/validations/format.rb +1 -1
  50. data/lib/active_model/validations/helper_methods.rb +13 -0
  51. data/lib/active_model/validations/inclusion.rb +3 -3
  52. data/lib/active_model/validations/length.rb +48 -17
  53. data/lib/active_model/validations/numericality.rb +12 -13
  54. data/lib/active_model/validations/validates.rb +1 -1
  55. data/lib/active_model/validations/with.rb +0 -10
  56. data/lib/active_model/validator.rb +6 -2
  57. data/lib/active_model/version.rb +1 -1
  58. metadata +34 -9
  59. 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,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,4 @@
1
+ require 'active_model/type/helpers/accepts_multiparameter_time'
2
+ require 'active_model/type/helpers/numeric'
3
+ require 'active_model/type/helpers/mutable'
4
+ require 'active_model/type/helpers/time_value'
@@ -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