activemodel 6.1.3.2 → 7.0.0.alpha2
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 +30 -73
- data/MIT-LICENSE +1 -1
- data/README.rdoc +3 -3
- data/lib/active_model/api.rb +99 -0
- data/lib/active_model/attribute.rb +4 -0
- data/lib/active_model/attribute_methods.rb +99 -52
- data/lib/active_model/attribute_set/builder.rb +1 -1
- data/lib/active_model/attribute_set.rb +4 -1
- data/lib/active_model/attributes.rb +15 -12
- data/lib/active_model/callbacks.rb +1 -1
- data/lib/active_model/conversion.rb +2 -2
- data/lib/active_model/dirty.rb +10 -4
- data/lib/active_model/errors.rb +20 -6
- data/lib/active_model/gem_version.rb +4 -4
- data/lib/active_model/locale/en.yml +1 -0
- data/lib/active_model/model.rb +6 -59
- data/lib/active_model/naming.rb +15 -8
- data/lib/active_model/serialization.rb +7 -2
- data/lib/active_model/translation.rb +1 -1
- data/lib/active_model/type/helpers/numeric.rb +9 -1
- data/lib/active_model/type/helpers/time_value.rb +2 -2
- data/lib/active_model/type/integer.rb +4 -1
- data/lib/active_model/type/registry.rb +9 -41
- data/lib/active_model/type/time.rb +1 -1
- data/lib/active_model/type.rb +6 -5
- data/lib/active_model/validations/absence.rb +1 -1
- data/lib/active_model/validations/clusivity.rb +1 -1
- data/lib/active_model/validations/comparability.rb +29 -0
- data/lib/active_model/validations/comparison.rb +82 -0
- data/lib/active_model/validations/confirmation.rb +4 -4
- data/lib/active_model/validations/numericality.rb +27 -20
- data/lib/active_model/validations.rb +4 -4
- data/lib/active_model/validator.rb +2 -2
- data/lib/active_model.rb +2 -1
- metadata +17 -14
@@ -12,9 +12,9 @@ module ActiveModel
|
|
12
12
|
|
13
13
|
if value.acts_like?(:time)
|
14
14
|
if is_utc?
|
15
|
-
value = value.getutc if
|
15
|
+
value = value.getutc if !value.utc?
|
16
16
|
else
|
17
|
-
value = value.getlocal
|
17
|
+
value = value.getlocal
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -1,70 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module ActiveModel
|
4
|
-
# :stopdoc:
|
5
4
|
module Type
|
6
|
-
class Registry
|
5
|
+
class Registry # :nodoc:
|
7
6
|
def initialize
|
8
|
-
@registrations =
|
7
|
+
@registrations = {}
|
9
8
|
end
|
10
9
|
|
11
|
-
def
|
10
|
+
def initialize_copy(other)
|
12
11
|
@registrations = @registrations.dup
|
13
12
|
super
|
14
13
|
end
|
15
14
|
|
16
|
-
def register(type_name, klass = nil,
|
15
|
+
def register(type_name, klass = nil, &block)
|
17
16
|
unless block_given?
|
18
17
|
block = proc { |_, *args| klass.new(*args) }
|
19
18
|
block.ruby2_keywords if block.respond_to?(:ruby2_keywords)
|
20
19
|
end
|
21
|
-
registrations
|
20
|
+
registrations[type_name] = block
|
22
21
|
end
|
23
22
|
|
24
|
-
def lookup(symbol, *args
|
25
|
-
registration =
|
23
|
+
def lookup(symbol, *args)
|
24
|
+
registration = registrations[symbol]
|
26
25
|
|
27
26
|
if registration
|
28
|
-
registration.call(
|
27
|
+
registration.call(symbol, *args)
|
29
28
|
else
|
30
29
|
raise ArgumentError, "Unknown type #{symbol.inspect}"
|
31
30
|
end
|
32
31
|
end
|
32
|
+
ruby2_keywords(:lookup)
|
33
33
|
|
34
34
|
private
|
35
35
|
attr_reader :registrations
|
36
|
-
|
37
|
-
def registration_klass
|
38
|
-
Registration
|
39
|
-
end
|
40
|
-
|
41
|
-
def find_registration(symbol, *args, **kwargs)
|
42
|
-
registrations.find { |r| r.matches?(symbol, *args, **kwargs) }
|
43
|
-
end
|
44
|
-
end
|
45
|
-
|
46
|
-
class Registration
|
47
|
-
# Options must be taken because of https://bugs.ruby-lang.org/issues/10856
|
48
|
-
def initialize(name, block, **)
|
49
|
-
@name = name
|
50
|
-
@block = block
|
51
|
-
end
|
52
|
-
|
53
|
-
def call(_registry, *args, **kwargs)
|
54
|
-
if kwargs.any? # https://bugs.ruby-lang.org/issues/10856
|
55
|
-
block.call(*args, **kwargs)
|
56
|
-
else
|
57
|
-
block.call(*args)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def matches?(type_name, *args, **kwargs)
|
62
|
-
type_name == name
|
63
|
-
end
|
64
|
-
|
65
|
-
private
|
66
|
-
attr_reader :name, :block
|
67
36
|
end
|
68
37
|
end
|
69
|
-
# :startdoc:
|
70
38
|
end
|
@@ -33,7 +33,7 @@ module ActiveModel
|
|
33
33
|
return apply_seconds_precision(value) unless value.is_a?(::String)
|
34
34
|
return if value.empty?
|
35
35
|
|
36
|
-
dummy_time_value = value.sub(/\A
|
36
|
+
dummy_time_value = value.sub(/\A\d{4}-\d\d-\d\d(?:T|\s)|/, "2000-01-01 ")
|
37
37
|
|
38
38
|
fast_string_to_time(dummy_time_value) || begin
|
39
39
|
time_hash = ::Date._parse(dummy_time_value)
|
data/lib/active_model/type.rb
CHANGED
@@ -24,13 +24,14 @@ module ActiveModel
|
|
24
24
|
class << self
|
25
25
|
attr_accessor :registry # :nodoc:
|
26
26
|
|
27
|
-
# Add a new type to the registry, allowing it to be
|
28
|
-
|
29
|
-
|
27
|
+
# Add a new type to the registry, allowing it to be referenced as a
|
28
|
+
# symbol by {attribute}[rdoc-ref:Attributes::ClassMethods#attribute].
|
29
|
+
def register(type_name, klass = nil, &block)
|
30
|
+
registry.register(type_name, klass, &block)
|
30
31
|
end
|
31
32
|
|
32
|
-
def lookup(
|
33
|
-
registry.lookup(
|
33
|
+
def lookup(...) # :nodoc:
|
34
|
+
registry.lookup(...)
|
34
35
|
end
|
35
36
|
|
36
37
|
def default_value # :nodoc:
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module ActiveModel
|
4
4
|
module Validations
|
5
5
|
# == \Active \Model Absence Validator
|
6
|
-
class AbsenceValidator < EachValidator
|
6
|
+
class AbsenceValidator < EachValidator # :nodoc:
|
7
7
|
def validate_each(record, attr_name, value)
|
8
8
|
record.errors.add(attr_name, :present, **options) if value.present?
|
9
9
|
end
|
@@ -4,7 +4,7 @@ require "active_support/core_ext/range"
|
|
4
4
|
|
5
5
|
module ActiveModel
|
6
6
|
module Validations
|
7
|
-
module Clusivity
|
7
|
+
module Clusivity # :nodoc:
|
8
8
|
ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \
|
9
9
|
"and must be supplied as the :in (or :within) option of the configuration hash"
|
10
10
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveModel
|
4
|
+
module Validations
|
5
|
+
module Comparability # :nodoc:
|
6
|
+
COMPARE_CHECKS = { greater_than: :>, greater_than_or_equal_to: :>=,
|
7
|
+
equal_to: :==, less_than: :<, less_than_or_equal_to: :<=,
|
8
|
+
other_than: :!= }.freeze
|
9
|
+
|
10
|
+
def option_value(record, option_value)
|
11
|
+
case option_value
|
12
|
+
when Proc
|
13
|
+
option_value.call(record)
|
14
|
+
when Symbol
|
15
|
+
record.send(option_value)
|
16
|
+
else
|
17
|
+
option_value
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def error_options(value, option_value)
|
22
|
+
options.except(*COMPARE_CHECKS.keys).merge!(
|
23
|
+
count: option_value,
|
24
|
+
value: value
|
25
|
+
)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_model/validations/comparability"
|
4
|
+
|
5
|
+
module ActiveModel
|
6
|
+
module Validations
|
7
|
+
class ComparisonValidator < EachValidator # :nodoc:
|
8
|
+
include Comparability
|
9
|
+
|
10
|
+
def check_validity!
|
11
|
+
unless (options.keys & COMPARE_CHECKS.keys).any?
|
12
|
+
raise ArgumentError, "Expected one of :greater_than, :greater_than_or_equal_to, "\
|
13
|
+
":equal_to, :less_than, :less_than_or_equal_to, or :other_than option to be supplied."
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def validate_each(record, attr_name, value)
|
18
|
+
options.slice(*COMPARE_CHECKS.keys).each do |option, raw_option_value|
|
19
|
+
option_value = option_value(record, raw_option_value)
|
20
|
+
|
21
|
+
if value.nil? || value.blank?
|
22
|
+
return record.errors.add(attr_name, :blank, **error_options(value, option_value))
|
23
|
+
end
|
24
|
+
|
25
|
+
unless value.public_send(COMPARE_CHECKS[option], option_value)
|
26
|
+
record.errors.add(attr_name, option, **error_options(value, option_value))
|
27
|
+
end
|
28
|
+
rescue ArgumentError => e
|
29
|
+
record.errors.add(attr_name, e.message)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module HelperMethods
|
35
|
+
# Validates the value of a specified attribute fulfills all
|
36
|
+
# defined comparisons with another value, proc, or attribute.
|
37
|
+
#
|
38
|
+
# class Person < ActiveRecord::Base
|
39
|
+
# validates_comparison_of :value, greater_than: 'the sum of its parts'
|
40
|
+
# end
|
41
|
+
#
|
42
|
+
# Configuration options:
|
43
|
+
# * <tt>:message</tt> - A custom error message (default is: "failed comparison").
|
44
|
+
# * <tt>:greater_than</tt> - Specifies the value must be greater than the
|
45
|
+
# supplied value.
|
46
|
+
# * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be
|
47
|
+
# greater than or equal to the supplied value.
|
48
|
+
# * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied
|
49
|
+
# value.
|
50
|
+
# * <tt>:less_than</tt> - Specifies the value must be less than the
|
51
|
+
# supplied value.
|
52
|
+
# * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less
|
53
|
+
# than or equal to the supplied value.
|
54
|
+
# * <tt>:other_than</tt> - Specifies the value must not be equal to the
|
55
|
+
# supplied value.
|
56
|
+
#
|
57
|
+
# There is also a list of default options supported by every validator:
|
58
|
+
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
|
59
|
+
# See <tt>ActiveModel::Validations#validates</tt> for more information
|
60
|
+
#
|
61
|
+
# The validator requires at least one of the following checks to be supplied.
|
62
|
+
# Each will accept a proc, value, or a symbol which corresponds to a method:
|
63
|
+
#
|
64
|
+
# * <tt>:greater_than</tt>
|
65
|
+
# * <tt>:greater_than_or_equal_to</tt>
|
66
|
+
# * <tt>:equal_to</tt>
|
67
|
+
# * <tt>:less_than</tt>
|
68
|
+
# * <tt>:less_than_or_equal_to</tt>
|
69
|
+
# * <tt>:other_than</tt>
|
70
|
+
#
|
71
|
+
# For example:
|
72
|
+
#
|
73
|
+
# class Person < ActiveRecord::Base
|
74
|
+
# validates_comparison_of :birth_date, less_than_or_equal_to: -> { Date.today }
|
75
|
+
# validates_comparison_of :preferred_name, other_than: :given_name, allow_nil: true
|
76
|
+
# end
|
77
|
+
def validates_comparison_of(*attr_names)
|
78
|
+
validates_with ComparisonValidator, _merge_attributes(attr_names)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -19,13 +19,13 @@ module ActiveModel
|
|
19
19
|
|
20
20
|
private
|
21
21
|
def setup!(klass)
|
22
|
-
klass.attr_reader(*attributes.
|
22
|
+
klass.attr_reader(*attributes.filter_map do |attribute|
|
23
23
|
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation")
|
24
|
-
end
|
24
|
+
end)
|
25
25
|
|
26
|
-
klass.attr_writer(*attributes.
|
26
|
+
klass.attr_writer(*attributes.filter_map do |attribute|
|
27
27
|
:"#{attribute}_confirmation" unless klass.method_defined?(:"#{attribute}_confirmation=")
|
28
|
-
end
|
28
|
+
end)
|
29
29
|
end
|
30
30
|
|
31
31
|
def confirmation_value_equal?(record, attribute, value, confirmed)
|
@@ -1,27 +1,34 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "active_model/validations/comparability"
|
3
4
|
require "bigdecimal/util"
|
4
5
|
|
5
6
|
module ActiveModel
|
6
7
|
module Validations
|
7
8
|
class NumericalityValidator < EachValidator # :nodoc:
|
8
|
-
|
9
|
-
equal_to: :==, less_than: :<, less_than_or_equal_to: :<=,
|
10
|
-
odd: :odd?, even: :even?, other_than: :!= }.freeze
|
9
|
+
include Comparability
|
11
10
|
|
12
|
-
|
11
|
+
RANGE_CHECKS = { in: :in? }
|
12
|
+
NUMBER_CHECKS = { odd: :odd?, even: :even? }
|
13
|
+
|
14
|
+
RESERVED_OPTIONS = COMPARE_CHECKS.keys + NUMBER_CHECKS.keys + RANGE_CHECKS.keys + [:only_integer]
|
13
15
|
|
14
16
|
INTEGER_REGEX = /\A[+-]?\d+\z/
|
15
17
|
|
16
18
|
HEXADECIMAL_REGEX = /\A[+-]?0[xX]/
|
17
19
|
|
18
20
|
def check_validity!
|
19
|
-
keys
|
20
|
-
options.slice(*keys).each do |option, value|
|
21
|
+
options.slice(*COMPARE_CHECKS.keys).each do |option, value|
|
21
22
|
unless value.is_a?(Numeric) || value.is_a?(Proc) || value.is_a?(Symbol)
|
22
23
|
raise ArgumentError, ":#{option} must be a number, a symbol or a proc"
|
23
24
|
end
|
24
25
|
end
|
26
|
+
|
27
|
+
options.slice(*RANGE_CHECKS.keys).each do |option, value|
|
28
|
+
unless value.is_a?(Range)
|
29
|
+
raise ArgumentError, ":#{option} must be a range"
|
30
|
+
end
|
31
|
+
end
|
25
32
|
end
|
26
33
|
|
27
34
|
def validate_each(record, attr_name, value, precision: Float::DIG, scale: nil)
|
@@ -37,23 +44,18 @@ module ActiveModel
|
|
37
44
|
|
38
45
|
value = parse_as_number(value, precision, scale)
|
39
46
|
|
40
|
-
options.slice(*
|
41
|
-
|
42
|
-
|
43
|
-
unless value.to_i.public_send(CHECKS[option])
|
47
|
+
options.slice(*RESERVED_OPTIONS).each do |option, option_value|
|
48
|
+
if NUMBER_CHECKS.include?(option)
|
49
|
+
unless value.to_i.public_send(NUMBER_CHECKS[option])
|
44
50
|
record.errors.add(attr_name, option, **filtered_options(value))
|
45
51
|
end
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
option_value = option_value.call(record)
|
50
|
-
when Symbol
|
51
|
-
option_value = record.send(option_value)
|
52
|
+
elsif RANGE_CHECKS.include?(option)
|
53
|
+
unless value.public_send(RANGE_CHECKS[option], option_value)
|
54
|
+
record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value))
|
52
55
|
end
|
53
|
-
|
54
|
-
option_value =
|
55
|
-
|
56
|
-
unless value.public_send(CHECKS[option], option_value)
|
56
|
+
elsif COMPARE_CHECKS.include?(option)
|
57
|
+
option_value = option_as_number(record, option_value, precision, scale)
|
58
|
+
unless value.public_send(COMPARE_CHECKS[option], option_value)
|
57
59
|
record.errors.add(attr_name, option, **filtered_options(value).merge!(count: option_value))
|
58
60
|
end
|
59
61
|
end
|
@@ -61,6 +63,10 @@ module ActiveModel
|
|
61
63
|
end
|
62
64
|
|
63
65
|
private
|
66
|
+
def option_as_number(record, option_value, precision, scale)
|
67
|
+
parse_as_number(option_value(record, option_value), precision, scale)
|
68
|
+
end
|
69
|
+
|
64
70
|
def parse_as_number(raw_value, precision, scale)
|
65
71
|
if raw_value.is_a?(Float)
|
66
72
|
parse_float(raw_value, precision, scale)
|
@@ -173,6 +179,7 @@ module ActiveModel
|
|
173
179
|
# supplied value.
|
174
180
|
# * <tt>:odd</tt> - Specifies the value must be an odd number.
|
175
181
|
# * <tt>:even</tt> - Specifies the value must be an even number.
|
182
|
+
# * <tt>:in</tt> - Check that the value is within a range.
|
176
183
|
#
|
177
184
|
# There is also a list of default options supported by every validator:
|
178
185
|
# +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
|
@@ -118,7 +118,7 @@ module ActiveModel
|
|
118
118
|
# end
|
119
119
|
# end
|
120
120
|
#
|
121
|
-
# Or with a block where self points to the current record to be validated:
|
121
|
+
# Or with a block where +self+ points to the current record to be validated:
|
122
122
|
#
|
123
123
|
# class Comment
|
124
124
|
# include ActiveModel::Validations
|
@@ -152,7 +152,7 @@ module ActiveModel
|
|
152
152
|
def validate(*args, &block)
|
153
153
|
options = args.extract_options!
|
154
154
|
|
155
|
-
if args.all?
|
155
|
+
if args.all?(Symbol)
|
156
156
|
options.each_key do |k|
|
157
157
|
unless VALID_OPTIONS_FOR_VALIDATE.include?(k)
|
158
158
|
raise ArgumentError.new("Unknown key: #{k.inspect}. Valid keys are: #{VALID_OPTIONS_FOR_VALIDATE.map(&:inspect).join(', ')}. Perhaps you meant to call `validates` instead of `validate`?")
|
@@ -272,7 +272,7 @@ module ActiveModel
|
|
272
272
|
end
|
273
273
|
|
274
274
|
# Copy validators on inheritance.
|
275
|
-
def inherited(base)
|
275
|
+
def inherited(base) # :nodoc:
|
276
276
|
dup = _validators.dup
|
277
277
|
base._validators = dup.each { |k, v| dup[k] = v.dup }
|
278
278
|
super
|
@@ -280,7 +280,7 @@ module ActiveModel
|
|
280
280
|
end
|
281
281
|
|
282
282
|
# Clean the +Errors+ object if instance is duped.
|
283
|
-
def initialize_dup(other)
|
283
|
+
def initialize_dup(other) # :nodoc:
|
284
284
|
@errors = nil
|
285
285
|
super
|
286
286
|
end
|
@@ -129,7 +129,7 @@ module ActiveModel
|
|
129
129
|
# record, attribute and value.
|
130
130
|
#
|
131
131
|
# All \Active \Model validations are built on top of this validator.
|
132
|
-
class EachValidator < Validator
|
132
|
+
class EachValidator < Validator
|
133
133
|
attr_reader :attributes
|
134
134
|
|
135
135
|
# Returns a new validator instance. All options will be available via the
|
@@ -174,7 +174,7 @@ module ActiveModel
|
|
174
174
|
|
175
175
|
# +BlockValidator+ is a special +EachValidator+ which receives a block on initialization
|
176
176
|
# and call this block for each attribute being validated. +validates_each+ uses this validator.
|
177
|
-
class BlockValidator < EachValidator
|
177
|
+
class BlockValidator < EachValidator # :nodoc:
|
178
178
|
def initialize(options, &block)
|
179
179
|
@block = block
|
180
180
|
super
|
data/lib/active_model.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
#--
|
4
|
-
# Copyright (c) 2004-
|
4
|
+
# Copyright (c) 2004-2021 David Heinemeier Hansson
|
5
5
|
#
|
6
6
|
# Permission is hereby granted, free of charge, to any person obtaining
|
7
7
|
# a copy of this software and associated documentation files (the
|
@@ -30,6 +30,7 @@ require "active_model/version"
|
|
30
30
|
module ActiveModel
|
31
31
|
extend ActiveSupport::Autoload
|
32
32
|
|
33
|
+
autoload :API
|
33
34
|
autoload :Attribute
|
34
35
|
autoload :Attributes
|
35
36
|
autoload :AttributeAssignment
|