activemodel 5.2.2.1 → 6.0.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +144 -51
- data/MIT-LICENSE +1 -1
- data/README.rdoc +4 -2
- data/lib/active_model.rb +1 -1
- data/lib/active_model/attribute.rb +3 -4
- data/lib/active_model/attribute/user_provided_default.rb +1 -2
- data/lib/active_model/attribute_assignment.rb +1 -1
- data/lib/active_model/attribute_methods.rb +54 -15
- data/lib/active_model/attribute_mutation_tracker.rb +88 -34
- data/lib/active_model/attribute_set.rb +2 -10
- data/lib/active_model/attribute_set/builder.rb +1 -3
- data/lib/active_model/attribute_set/yaml_encoder.rb +1 -2
- data/lib/active_model/attributes.rb +60 -33
- data/lib/active_model/callbacks.rb +10 -7
- data/lib/active_model/conversion.rb +1 -1
- data/lib/active_model/dirty.rb +36 -99
- data/lib/active_model/errors.rb +104 -20
- data/lib/active_model/gem_version.rb +3 -3
- data/lib/active_model/naming.rb +19 -3
- data/lib/active_model/railtie.rb +6 -0
- data/lib/active_model/secure_password.rb +47 -48
- data/lib/active_model/serializers/json.rb +10 -9
- data/lib/active_model/type/binary.rb +1 -1
- data/lib/active_model/type/boolean.rb +10 -1
- data/lib/active_model/type/date.rb +2 -5
- data/lib/active_model/type/date_time.rb +4 -7
- data/lib/active_model/type/float.rb +0 -2
- data/lib/active_model/type/helpers.rb +1 -0
- data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +4 -0
- data/lib/active_model/type/helpers/numeric.rb +9 -2
- data/lib/active_model/type/helpers/time_value.rb +16 -15
- data/lib/active_model/type/helpers/timezone.rb +19 -0
- data/lib/active_model/type/integer.rb +7 -19
- data/lib/active_model/type/registry.rb +2 -10
- data/lib/active_model/type/string.rb +2 -2
- data/lib/active_model/type/time.rb +2 -1
- data/lib/active_model/validations.rb +0 -2
- data/lib/active_model/validations/acceptance.rb +33 -25
- data/lib/active_model/validations/clusivity.rb +1 -1
- data/lib/active_model/validations/confirmation.rb +2 -2
- data/lib/active_model/validations/inclusion.rb +1 -1
- data/lib/active_model/validations/length.rb +1 -1
- data/lib/active_model/validations/numericality.rb +20 -11
- data/lib/active_model/validations/validates.rb +2 -2
- data/lib/active_model/validator.rb +1 -1
- metadata +13 -9
@@ -14,7 +14,16 @@ module ActiveModel
|
|
14
14
|
# - Empty strings are coerced to +nil+
|
15
15
|
# - All other values will be coerced to +true+
|
16
16
|
class Boolean < Value
|
17
|
-
FALSE_VALUES = [
|
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
|
18
27
|
|
19
28
|
def type # :nodoc:
|
20
29
|
:boolean
|
@@ -3,16 +3,13 @@
|
|
3
3
|
module ActiveModel
|
4
4
|
module Type
|
5
5
|
class Date < Value # :nodoc:
|
6
|
+
include Helpers::Timezone
|
6
7
|
include Helpers::AcceptsMultiparameterTime.new
|
7
8
|
|
8
9
|
def type
|
9
10
|
:date
|
10
11
|
end
|
11
12
|
|
12
|
-
def serialize(value)
|
13
|
-
cast(value)
|
14
|
-
end
|
15
|
-
|
16
13
|
def type_cast_for_schema(value)
|
17
14
|
value.to_s(:db).inspect
|
18
15
|
end
|
@@ -49,7 +46,7 @@ module ActiveModel
|
|
49
46
|
|
50
47
|
def value_from_multiparameter_assignment(*)
|
51
48
|
time = super
|
52
|
-
time && time.
|
49
|
+
time && new_date(time.year, time.mon, time.mday)
|
53
50
|
end
|
54
51
|
end
|
55
52
|
end
|
@@ -3,6 +3,7 @@
|
|
3
3
|
module ActiveModel
|
4
4
|
module Type
|
5
5
|
class DateTime < Value # :nodoc:
|
6
|
+
include Helpers::Timezone
|
6
7
|
include Helpers::TimeValue
|
7
8
|
include Helpers::AcceptsMultiparameterTime.new(
|
8
9
|
defaults: { 4 => 0, 5 => 0 }
|
@@ -12,10 +13,6 @@ module ActiveModel
|
|
12
13
|
:datetime
|
13
14
|
end
|
14
15
|
|
15
|
-
def serialize(value)
|
16
|
-
super(cast(value))
|
17
|
-
end
|
18
|
-
|
19
16
|
private
|
20
17
|
|
21
18
|
def cast_value(value)
|
@@ -39,9 +36,9 @@ module ActiveModel
|
|
39
36
|
end
|
40
37
|
|
41
38
|
def value_from_multiparameter_assignment(values_hash)
|
42
|
-
|
43
|
-
if
|
44
|
-
raise ArgumentError,
|
39
|
+
missing_parameters = (1..3).select { |key| !values_hash.key?(key) }
|
40
|
+
if missing_parameters.any?
|
41
|
+
raise ArgumentError, "Provided hash #{values_hash} doesn't contain necessary keys: #{missing_parameters}"
|
45
42
|
end
|
46
43
|
super
|
47
44
|
end
|
@@ -5,6 +5,10 @@ module ActiveModel
|
|
5
5
|
module Helpers # :nodoc: all
|
6
6
|
class AcceptsMultiparameterTime < Module
|
7
7
|
def initialize(defaults: {})
|
8
|
+
define_method(:serialize) do |value|
|
9
|
+
super(cast(value))
|
10
|
+
end
|
11
|
+
|
8
12
|
define_method(:cast) do |value|
|
9
13
|
if value.is_a?(Hash)
|
10
14
|
value_from_multiparameter_assignment(value)
|
@@ -4,6 +4,10 @@ module ActiveModel
|
|
4
4
|
module Type
|
5
5
|
module Helpers # :nodoc: all
|
6
6
|
module Numeric
|
7
|
+
def serialize(value)
|
8
|
+
cast(value)
|
9
|
+
end
|
10
|
+
|
7
11
|
def cast(value)
|
8
12
|
value = \
|
9
13
|
case value
|
@@ -22,15 +26,18 @@ module ActiveModel
|
|
22
26
|
private
|
23
27
|
|
24
28
|
def number_to_non_number?(old_value, new_value_before_type_cast)
|
25
|
-
old_value != nil && non_numeric_string?(new_value_before_type_cast)
|
29
|
+
old_value != nil && non_numeric_string?(new_value_before_type_cast.to_s)
|
26
30
|
end
|
27
31
|
|
28
32
|
def non_numeric_string?(value)
|
29
33
|
# 'wibble'.to_i will give zero, we want to make sure
|
30
34
|
# that we aren't marking int zero to string zero as
|
31
35
|
# changed.
|
32
|
-
|
36
|
+
!NUMERIC_REGEX.match?(value)
|
33
37
|
end
|
38
|
+
|
39
|
+
NUMERIC_REGEX = /\A\s*[+-]?\d/
|
40
|
+
private_constant :NUMERIC_REGEX
|
34
41
|
end
|
35
42
|
end
|
36
43
|
end
|
@@ -21,25 +21,20 @@ module ActiveModel
|
|
21
21
|
value
|
22
22
|
end
|
23
23
|
|
24
|
-
def
|
25
|
-
|
26
|
-
|
24
|
+
def apply_seconds_precision(value)
|
25
|
+
return value unless precision && value.respond_to?(:nsec)
|
26
|
+
|
27
|
+
number_of_insignificant_digits = 9 - precision
|
28
|
+
round_power = 10**number_of_insignificant_digits
|
29
|
+
rounded_off_nsec = value.nsec % round_power
|
27
30
|
|
28
|
-
|
29
|
-
|
30
|
-
:utc
|
31
|
+
if rounded_off_nsec > 0
|
32
|
+
value.change(nsec: value.nsec - rounded_off_nsec)
|
31
33
|
else
|
32
|
-
|
34
|
+
value
|
33
35
|
end
|
34
36
|
end
|
35
37
|
|
36
|
-
def apply_seconds_precision(value)
|
37
|
-
return value unless precision && value.respond_to?(:usec)
|
38
|
-
number_of_insignificant_digits = 6 - precision
|
39
|
-
round_power = 10**number_of_insignificant_digits
|
40
|
-
value.change(usec: value.usec - value.usec % round_power)
|
41
|
-
end
|
42
|
-
|
43
38
|
def type_cast_for_schema(value)
|
44
39
|
value.to_s(:db).inspect
|
45
40
|
end
|
@@ -70,7 +65,13 @@ module ActiveModel
|
|
70
65
|
# Doesn't handle time zones.
|
71
66
|
def fast_string_to_time(string)
|
72
67
|
if string =~ ISO_DATETIME
|
73
|
-
|
68
|
+
microsec_part = $7
|
69
|
+
if microsec_part && microsec_part.start_with?(".") && microsec_part.length == 7
|
70
|
+
microsec_part[0] = ""
|
71
|
+
microsec = microsec_part.to_i
|
72
|
+
else
|
73
|
+
microsec = (microsec_part.to_r * 1_000_000).to_i
|
74
|
+
end
|
74
75
|
new_time $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, microsec
|
75
76
|
end
|
76
77
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/core_ext/time/zones"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Type
|
7
|
+
module Helpers # :nodoc: all
|
8
|
+
module Timezone
|
9
|
+
def is_utc?
|
10
|
+
::Time.zone_default.nil? || ::Time.zone_default =~ "UTC"
|
11
|
+
end
|
12
|
+
|
13
|
+
def default_timezone
|
14
|
+
is_utc? ? :utc : :local
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -19,39 +19,27 @@ module ActiveModel
|
|
19
19
|
end
|
20
20
|
|
21
21
|
def deserialize(value)
|
22
|
-
return if value.
|
22
|
+
return if value.blank?
|
23
23
|
value.to_i
|
24
24
|
end
|
25
25
|
|
26
26
|
def serialize(value)
|
27
|
-
|
28
|
-
|
29
|
-
ensure_in_range(result)
|
30
|
-
end
|
31
|
-
result
|
27
|
+
return if value.is_a?(::String) && non_numeric_string?(value)
|
28
|
+
ensure_in_range(super)
|
32
29
|
end
|
33
30
|
|
34
|
-
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
35
|
-
# Workaround for Ruby 2.2 "private attribute?" warning.
|
36
|
-
protected
|
37
|
-
|
38
|
-
attr_reader :range
|
39
|
-
|
40
31
|
private
|
32
|
+
attr_reader :range
|
41
33
|
|
42
34
|
def cast_value(value)
|
43
|
-
|
44
|
-
when true then 1
|
45
|
-
when false then 0
|
46
|
-
else
|
47
|
-
value.to_i rescue nil
|
48
|
-
end
|
35
|
+
value.to_i rescue nil
|
49
36
|
end
|
50
37
|
|
51
38
|
def ensure_in_range(value)
|
52
|
-
|
39
|
+
if value && !range.cover?(value)
|
53
40
|
raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
|
54
41
|
end
|
42
|
+
value
|
55
43
|
end
|
56
44
|
|
57
45
|
def max_value
|
@@ -23,13 +23,8 @@ module ActiveModel
|
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
26
|
-
# TODO Change this to private once we've dropped Ruby 2.2 support.
|
27
|
-
# Workaround for Ruby 2.2 "private attribute?" warning.
|
28
|
-
protected
|
29
|
-
|
30
|
-
attr_reader :registrations
|
31
|
-
|
32
26
|
private
|
27
|
+
attr_reader :registrations
|
33
28
|
|
34
29
|
def registration_klass
|
35
30
|
Registration
|
@@ -59,10 +54,7 @@ module ActiveModel
|
|
59
54
|
type_name == name
|
60
55
|
end
|
61
56
|
|
62
|
-
|
63
|
-
# Workaround for Ruby 2.2 "private attribute?" warning.
|
64
|
-
protected
|
65
|
-
|
57
|
+
private
|
66
58
|
attr_reader :name, :block
|
67
59
|
end
|
68
60
|
end
|
@@ -3,9 +3,10 @@
|
|
3
3
|
module ActiveModel
|
4
4
|
module Type
|
5
5
|
class Time < Value # :nodoc:
|
6
|
+
include Helpers::Timezone
|
6
7
|
include Helpers::TimeValue
|
7
8
|
include Helpers::AcceptsMultiparameterTime.new(
|
8
|
-
defaults: { 1 =>
|
9
|
+
defaults: { 1 => 2000, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
|
9
10
|
)
|
10
11
|
|
11
12
|
def type
|
@@ -17,7 +17,8 @@ module ActiveModel
|
|
17
17
|
private
|
18
18
|
|
19
19
|
def setup!(klass)
|
20
|
-
|
20
|
+
define_attributes = LazilyDefineAttributes.new(attributes)
|
21
|
+
klass.include(define_attributes) unless klass.included_modules.include?(define_attributes)
|
21
22
|
end
|
22
23
|
|
23
24
|
def acceptable_option?(value)
|
@@ -25,50 +26,57 @@ module ActiveModel
|
|
25
26
|
end
|
26
27
|
|
27
28
|
class LazilyDefineAttributes < Module
|
28
|
-
def initialize(
|
29
|
+
def initialize(attributes)
|
30
|
+
@attributes = attributes.map(&:to_s)
|
31
|
+
end
|
32
|
+
|
33
|
+
def included(klass)
|
34
|
+
@lock = Mutex.new
|
35
|
+
mod = self
|
36
|
+
|
29
37
|
define_method(:respond_to_missing?) do |method_name, include_private = false|
|
30
|
-
|
38
|
+
mod.define_on(klass)
|
39
|
+
super(method_name, include_private) || mod.matches?(method_name)
|
31
40
|
end
|
32
41
|
|
33
42
|
define_method(:method_missing) do |method_name, *args, &block|
|
34
|
-
|
35
|
-
|
43
|
+
mod.define_on(klass)
|
44
|
+
if mod.matches?(method_name)
|
36
45
|
send(method_name, *args, &block)
|
37
46
|
else
|
38
47
|
super(method_name, *args, &block)
|
39
48
|
end
|
40
49
|
end
|
41
50
|
end
|
42
|
-
end
|
43
|
-
|
44
|
-
class AttributeDefinition
|
45
|
-
def initialize(attributes)
|
46
|
-
@attributes = attributes.map(&:to_s)
|
47
|
-
end
|
48
51
|
|
49
52
|
def matches?(method_name)
|
50
|
-
attr_name =
|
51
|
-
attributes.
|
53
|
+
attr_name = method_name.to_s.chomp("=")
|
54
|
+
attributes.any? { |name| name == attr_name }
|
52
55
|
end
|
53
56
|
|
54
57
|
def define_on(klass)
|
55
|
-
|
56
|
-
|
57
|
-
klass.send(:attr_reader, *attr_readers)
|
58
|
-
klass.send(:attr_writer, *attr_writers)
|
59
|
-
end
|
58
|
+
@lock&.synchronize do
|
59
|
+
return unless @lock
|
60
60
|
|
61
|
-
|
62
|
-
|
63
|
-
protected
|
61
|
+
attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
|
62
|
+
attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
|
64
63
|
|
65
|
-
|
64
|
+
attr_reader(*attr_readers)
|
65
|
+
attr_writer(*attr_writers)
|
66
66
|
|
67
|
-
|
67
|
+
remove_method :respond_to_missing?
|
68
|
+
remove_method :method_missing
|
68
69
|
|
69
|
-
|
70
|
-
method_name.to_s.chomp("=")
|
70
|
+
@lock = nil
|
71
71
|
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def ==(other)
|
75
|
+
self.class == other.class && attributes == other.attributes
|
76
|
+
end
|
77
|
+
|
78
|
+
protected
|
79
|
+
attr_reader :attributes
|
72
80
|
end
|
73
81
|
end
|
74
82
|
|
@@ -32,7 +32,7 @@ module ActiveModel
|
|
32
32
|
@delimiter ||= options[:in] || options[:within]
|
33
33
|
end
|
34
34
|
|
35
|
-
#
|
35
|
+
# After Ruby 2.2, <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
|
36
36
|
# possible values in the range for equality, which is slower but more accurate.
|
37
37
|
# <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
|
38
38
|
# endpoints, which is fast but is only accurate on Numeric, Time, Date,
|
@@ -19,11 +19,11 @@ module ActiveModel
|
|
19
19
|
|
20
20
|
private
|
21
21
|
def setup!(klass)
|
22
|
-
klass.
|
22
|
+
klass.attr_reader(*attributes.map do |attribute|
|
23
23
|
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation")
|
24
24
|
end.compact)
|
25
25
|
|
26
|
-
klass.
|
26
|
+
klass.attr_writer(*attributes.map do |attribute|
|
27
27
|
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=")
|
28
28
|
end.compact)
|
29
29
|
end
|
@@ -19,7 +19,7 @@ module ActiveModel
|
|
19
19
|
# particular enumerable object.
|
20
20
|
#
|
21
21
|
# class Person < ActiveRecord::Base
|
22
|
-
# validates_inclusion_of :
|
22
|
+
# validates_inclusion_of :role, in: %w( admin contributor )
|
23
23
|
# validates_inclusion_of :age, in: 0..99
|
24
24
|
# validates_inclusion_of :format, in: %w( jpg gif png ), message: "extension %{value} is not included in the list"
|
25
25
|
# validates_inclusion_of :states, in: ->(person) { STATES[person.country] }
|