activemodel 7.0.8 → 7.1.0

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.
Files changed (65) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +145 -182
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +9 -9
  5. data/lib/active_model/access.rb +16 -0
  6. data/lib/active_model/api.rb +5 -5
  7. data/lib/active_model/attribute/user_provided_default.rb +4 -0
  8. data/lib/active_model/attribute.rb +26 -1
  9. data/lib/active_model/attribute_assignment.rb +1 -1
  10. data/lib/active_model/attribute_methods.rb +102 -63
  11. data/lib/active_model/attribute_registration.rb +77 -0
  12. data/lib/active_model/attribute_set.rb +9 -0
  13. data/lib/active_model/attributes.rb +62 -45
  14. data/lib/active_model/callbacks.rb +5 -5
  15. data/lib/active_model/conversion.rb +14 -4
  16. data/lib/active_model/deprecator.rb +7 -0
  17. data/lib/active_model/dirty.rb +134 -13
  18. data/lib/active_model/error.rb +4 -3
  19. data/lib/active_model/errors.rb +11 -6
  20. data/lib/active_model/forbidden_attributes_protection.rb +2 -0
  21. data/lib/active_model/gem_version.rb +3 -3
  22. data/lib/active_model/lint.rb +1 -1
  23. data/lib/active_model/locale/en.yml +1 -0
  24. data/lib/active_model/model.rb +26 -2
  25. data/lib/active_model/naming.rb +29 -10
  26. data/lib/active_model/railtie.rb +4 -0
  27. data/lib/active_model/secure_password.rb +61 -23
  28. data/lib/active_model/serialization.rb +3 -3
  29. data/lib/active_model/serializers/json.rb +1 -1
  30. data/lib/active_model/translation.rb +18 -16
  31. data/lib/active_model/type/big_integer.rb +23 -1
  32. data/lib/active_model/type/binary.rb +7 -1
  33. data/lib/active_model/type/boolean.rb +11 -9
  34. data/lib/active_model/type/date.rb +28 -2
  35. data/lib/active_model/type/date_time.rb +45 -3
  36. data/lib/active_model/type/decimal.rb +39 -1
  37. data/lib/active_model/type/float.rb +30 -1
  38. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +5 -1
  39. data/lib/active_model/type/helpers/numeric.rb +4 -0
  40. data/lib/active_model/type/helpers/time_value.rb +28 -12
  41. data/lib/active_model/type/immutable_string.rb +37 -1
  42. data/lib/active_model/type/integer.rb +44 -1
  43. data/lib/active_model/type/serialize_cast_value.rb +47 -0
  44. data/lib/active_model/type/string.rb +9 -1
  45. data/lib/active_model/type/time.rb +48 -7
  46. data/lib/active_model/type/value.rb +17 -1
  47. data/lib/active_model/type.rb +1 -0
  48. data/lib/active_model/validations/absence.rb +1 -1
  49. data/lib/active_model/validations/acceptance.rb +1 -1
  50. data/lib/active_model/validations/callbacks.rb +4 -4
  51. data/lib/active_model/validations/clusivity.rb +5 -8
  52. data/lib/active_model/validations/comparability.rb +0 -11
  53. data/lib/active_model/validations/comparison.rb +15 -7
  54. data/lib/active_model/validations/format.rb +6 -7
  55. data/lib/active_model/validations/length.rb +10 -8
  56. data/lib/active_model/validations/numericality.rb +35 -23
  57. data/lib/active_model/validations/presence.rb +1 -1
  58. data/lib/active_model/validations/resolve_value.rb +26 -0
  59. data/lib/active_model/validations/validates.rb +3 -3
  60. data/lib/active_model/validations/with.rb +9 -2
  61. data/lib/active_model/validations.rb +44 -9
  62. data/lib/active_model/validator.rb +7 -5
  63. data/lib/active_model/version.rb +1 -1
  64. data/lib/active_model.rb +5 -1
  65. metadata +12 -7
@@ -2,12 +2,45 @@
2
2
 
3
3
  module ActiveModel
4
4
  module Type
5
- class Time < Value # :nodoc:
5
+ # = Active Model \Time \Type
6
+ #
7
+ # Attribute type for time of day representation. It is registered under the
8
+ # +:time+ key.
9
+ #
10
+ # class Event
11
+ # include ActiveModel::Attributes
12
+ #
13
+ # attribute :start, :time
14
+ # end
15
+ #
16
+ # String values are parsed using the ISO 8601 datetime format, but are
17
+ # normalized to have a date of 2000-01-01 and be in the UTC time zone.
18
+ #
19
+ # event = Event.new
20
+ # event.start = "2004-10-25T01:23:45-06:00"
21
+ #
22
+ # event.start.class # => Time
23
+ # event.start # => 2000-01-01 07:23:45 UTC
24
+ #
25
+ # Partial time-only formats are also accepted.
26
+ #
27
+ # event.start = "00:01:02+03:00"
28
+ # event.start # => 1999-12-31 21:01:02 UTC
29
+ #
30
+ # The degree of sub-second precision can be customized when declaring an
31
+ # attribute:
32
+ #
33
+ # class Event
34
+ # include ActiveModel::Attributes
35
+ #
36
+ # attribute :start, :time, precision: 4
37
+ # end
38
+ class Time < Value
6
39
  include Helpers::Timezone
7
- include Helpers::TimeValue
8
40
  include Helpers::AcceptsMultiparameterTime.new(
9
41
  defaults: { 1 => 2000, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
10
42
  )
43
+ include Helpers::TimeValue
11
44
 
12
45
  def type
13
46
  :time
@@ -19,8 +52,12 @@ module ActiveModel
19
52
  case value
20
53
  when ::String
21
54
  value = "2000-01-01 #{value}"
22
- time_hash = ::Date._parse(value)
23
- return if time_hash[:hour].nil?
55
+ time_hash = begin
56
+ ::Date._parse(value)
57
+ rescue ArgumentError
58
+ end
59
+
60
+ return if time_hash.nil? || time_hash[:hour].nil?
24
61
  when ::Time
25
62
  value = value.change(year: 2000, day: 1, month: 1)
26
63
  end
@@ -31,13 +68,17 @@ module ActiveModel
31
68
  private
32
69
  def cast_value(value)
33
70
  return apply_seconds_precision(value) unless value.is_a?(::String)
34
- return if value.empty?
71
+ return if value.blank?
35
72
 
36
73
  dummy_time_value = value.sub(/\A\d{4}-\d\d-\d\d(?:T|\s)|/, "2000-01-01 ")
37
74
 
38
75
  fast_string_to_time(dummy_time_value) || begin
39
- time_hash = ::Date._parse(dummy_time_value)
40
- return if time_hash[:hour].nil?
76
+ time_hash = begin
77
+ ::Date._parse(dummy_time_value)
78
+ rescue ArgumentError
79
+ end
80
+
81
+ return if time_hash.nil? || time_hash[:hour].nil?
41
82
  new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
42
83
  end
43
84
  end
@@ -2,10 +2,20 @@
2
2
 
3
3
  module ActiveModel
4
4
  module Type
5
+ # = Active Model \Value \Type
6
+ #
7
+ # The base class for all attribute types. This class also serves as the
8
+ # default type for attributes that do not specify a type.
5
9
  class Value
10
+ include SerializeCastValue
6
11
  attr_reader :precision, :scale, :limit
7
12
 
13
+ # Initializes a type with three basic configuration settings: precision,
14
+ # limit, and scale. The Value base class does not define behavior for
15
+ # these settings. It uses them for equality comparison and hash key
16
+ # generation only.
8
17
  def initialize(precision: nil, limit: nil, scale: nil)
18
+ super()
9
19
  @precision = precision
10
20
  @scale = scale
11
21
  @limit = limit
@@ -19,7 +29,9 @@ module ActiveModel
19
29
  true
20
30
  end
21
31
 
22
- def type # :nodoc:
32
+ # Returns the unique type name as a Symbol. Subclasses should override
33
+ # this method.
34
+ def type
23
35
  end
24
36
 
25
37
  # Converts a value from database input to the appropriate ruby type. The
@@ -129,6 +141,10 @@ module ActiveModel
129
141
  false
130
142
  end
131
143
 
144
+ def as_json(*)
145
+ raise NoMethodError
146
+ end
147
+
132
148
  private
133
149
  # Convenience method for types which do not need separate type casting
134
150
  # behavior for user and database inputs. Called by Value#cast for
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_model/type/helpers"
4
+ require "active_model/type/serialize_cast_value"
4
5
  require "active_model/type/value"
5
6
 
6
7
  require "active_model/type/big_integer"
@@ -11,7 +11,7 @@ module ActiveModel
11
11
 
12
12
  module HelperMethods
13
13
  # Validates that the specified attributes are blank (as defined by
14
- # Object#present?). Happens by default on save.
14
+ # Object#present?).
15
15
  #
16
16
  # class Person < ActiveRecord::Base
17
17
  # validates_absence_of :first_name
@@ -90,7 +90,7 @@ module ActiveModel
90
90
  #
91
91
  # If the database column does not exist, the +terms_of_service+ attribute
92
92
  # is entirely virtual. This check is performed only if +terms_of_service+
93
- # is not +nil+ and by default on save.
93
+ # is not +nil+.
94
94
  #
95
95
  # Configuration options:
96
96
  # * <tt>:message</tt> - A custom error message (default is: "must be
@@ -2,12 +2,12 @@
2
2
 
3
3
  module ActiveModel
4
4
  module Validations
5
- # == Active \Model \Validation \Callbacks
5
+ # = Active \Model \Validation \Callbacks
6
6
  #
7
- # Provides an interface for any class to have +before_validation+ and
8
- # +after_validation+ callbacks.
7
+ # Provides an interface for any class to have ClassMethods#before_validation and
8
+ # ClassMethods#after_validation callbacks.
9
9
  #
10
- # First, include ActiveModel::Validations::Callbacks from the class you are
10
+ # First, include +ActiveModel::Validations::Callbacks+ from the class you are
11
11
  # creating:
12
12
  #
13
13
  # class MyModel
@@ -1,10 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_model/validations/resolve_value"
3
4
  require "active_support/core_ext/range"
4
5
 
5
6
  module ActiveModel
6
7
  module Validations
7
8
  module Clusivity # :nodoc:
9
+ include ResolveValue
10
+
8
11
  ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \
9
12
  "and must be supplied as the :in (or :within) option of the configuration hash"
10
13
 
@@ -16,13 +19,7 @@ module ActiveModel
16
19
 
17
20
  private
18
21
  def include?(record, value)
19
- members = if delimiter.respond_to?(:call)
20
- delimiter.call(record)
21
- elsif delimiter.respond_to?(:to_sym)
22
- record.send(delimiter)
23
- else
24
- delimiter
25
- end
22
+ members = resolve_value(record, delimiter)
26
23
 
27
24
  if value.is_a?(Array)
28
25
  value.all? { |v| members.public_send(inclusion_method(members), v) }
@@ -42,7 +39,7 @@ module ActiveModel
42
39
  # or DateTime ranges.
43
40
  def inclusion_method(enumerable)
44
41
  if enumerable.is_a? Range
45
- case enumerable.first
42
+ case enumerable.begin || enumerable.end
46
43
  when Numeric, Time, DateTime, Date
47
44
  :cover?
48
45
  else
@@ -7,17 +7,6 @@ module ActiveModel
7
7
  equal_to: :==, less_than: :<, less_than_or_equal_to: :<=,
8
8
  other_than: :!= }.freeze
9
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
10
  def error_options(value, option_value)
22
11
  options.except(*COMPARE_CHECKS.keys).merge!(
23
12
  count: option_value,
@@ -1,11 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_model/validations/comparability"
4
+ require "active_model/validations/resolve_value"
4
5
 
5
6
  module ActiveModel
6
7
  module Validations
7
8
  class ComparisonValidator < EachValidator # :nodoc:
8
9
  include Comparability
10
+ include ResolveValue
9
11
 
10
12
  def check_validity!
11
13
  unless (options.keys & COMPARE_CHECKS.keys).any?
@@ -16,7 +18,7 @@ module ActiveModel
16
18
 
17
19
  def validate_each(record, attr_name, value)
18
20
  options.slice(*COMPARE_CHECKS.keys).each do |option, raw_option_value|
19
- option_value = option_value(record, raw_option_value)
21
+ option_value = resolve_value(record, raw_option_value)
20
22
 
21
23
  if value.nil? || value.blank?
22
24
  return record.errors.add(attr_name, :blank, **error_options(value, option_value))
@@ -42,17 +44,23 @@ module ActiveModel
42
44
  # Configuration options:
43
45
  # * <tt>:message</tt> - A custom error message (default is: "failed comparison").
44
46
  # * <tt>:greater_than</tt> - Specifies the value must be greater than the
45
- # supplied value.
47
+ # supplied value. The default error message for this option is _"must be
48
+ # greater than %{count}"_.
46
49
  # * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be
47
- # greater than or equal to the supplied value.
50
+ # greater than or equal to the supplied value. The default error message
51
+ # for this option is _"must be greater than or equal to %{count}"_.
48
52
  # * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied
49
- # value.
53
+ # value. The default error message for this option is _"must be equal to
54
+ # %{count}"_.
50
55
  # * <tt>:less_than</tt> - Specifies the value must be less than the
51
- # supplied value.
56
+ # supplied value. The default error message for this option is _"must be
57
+ # less than %{count}"_.
52
58
  # * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less
53
- # than or equal to the supplied value.
59
+ # than or equal to the supplied value. The default error message for
60
+ # this option is _"must be less than or equal to %{count}"_.
54
61
  # * <tt>:other_than</tt> - Specifies the value must not be equal to the
55
- # supplied value.
62
+ # supplied value. The default error message for this option is _"must be
63
+ # other than %{count}"_.
56
64
  #
57
65
  # There is also a list of default options supported by every validator:
58
66
  # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
@@ -1,14 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_model/validations/resolve_value"
4
+
3
5
  module ActiveModel
4
6
  module Validations
5
7
  class FormatValidator < EachValidator # :nodoc:
8
+ include ResolveValue
9
+
6
10
  def validate_each(record, attribute, value)
7
11
  if options[:with]
8
- regexp = option_call(record, :with)
12
+ regexp = resolve_value(record, options[:with])
9
13
  record_error(record, attribute, :with, value) unless regexp.match?(value.to_s)
10
14
  elsif options[:without]
11
- regexp = option_call(record, :without)
15
+ regexp = resolve_value(record, options[:without])
12
16
  record_error(record, attribute, :without, value) if regexp.match?(value.to_s)
13
17
  end
14
18
  end
@@ -23,11 +27,6 @@ module ActiveModel
23
27
  end
24
28
 
25
29
  private
26
- def option_call(record, name)
27
- option = options[name]
28
- option.respond_to?(:call) ? option.call(record) : option
29
- end
30
-
31
30
  def record_error(record, attribute, name, value)
32
31
  record.errors.add(attribute, :invalid, **options.except(name).merge!(value: value))
33
32
  end
@@ -1,8 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "active_model/validations/resolve_value"
4
+
3
5
  module ActiveModel
4
6
  module Validations
5
7
  class LengthValidator < EachValidator # :nodoc:
8
+ include ResolveValue
9
+
6
10
  MESSAGES = { is: :wrong_length, minimum: :too_short, maximum: :too_long }.freeze
7
11
  CHECKS = { is: :==, minimum: :>=, maximum: :<= }.freeze
8
12
 
@@ -11,7 +15,8 @@ module ActiveModel
11
15
  def initialize(options)
12
16
  if range = (options.delete(:in) || options.delete(:within))
13
17
  raise ArgumentError, ":in and :within must be a Range" unless range.is_a?(Range)
14
- options[:minimum], options[:maximum] = range.min, range.max
18
+ options[:minimum] = range.min if range.begin
19
+ options[:maximum] = (range.exclude_end? ? range.end - 1 : range.end) if range.end
15
20
  end
16
21
 
17
22
  if options[:allow_blank] == false && options[:minimum].nil? && options[:is].nil?
@@ -31,7 +36,9 @@ module ActiveModel
31
36
  keys.each do |key|
32
37
  value = options[key]
33
38
 
34
- unless (value.is_a?(Integer) && value >= 0) || value == Float::INFINITY || value.is_a?(Symbol) || value.is_a?(Proc)
39
+ unless (value.is_a?(Integer) && value >= 0) ||
40
+ value == Float::INFINITY || value == -Float::INFINITY ||
41
+ value.is_a?(Symbol) || value.is_a?(Proc)
35
42
  raise ArgumentError, ":#{key} must be a non-negative Integer, Infinity, Symbol, or Proc"
36
43
  end
37
44
  end
@@ -45,12 +52,7 @@ module ActiveModel
45
52
  next unless check_value = options[key]
46
53
 
47
54
  if !value.nil? || skip_nil_check?(key)
48
- case check_value
49
- when Proc
50
- check_value = check_value.call(record)
51
- when Symbol
52
- check_value = record.send(check_value)
53
- end
55
+ check_value = resolve_value(record, check_value)
54
56
  next if value_length.public_send(validity_check, check_value)
55
57
  end
56
58
 
@@ -1,17 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "active_model/validations/comparability"
4
+ require "active_model/validations/resolve_value"
4
5
  require "bigdecimal/util"
5
6
 
6
7
  module ActiveModel
7
8
  module Validations
8
9
  class NumericalityValidator < EachValidator # :nodoc:
9
10
  include Comparability
11
+ include ResolveValue
10
12
 
11
13
  RANGE_CHECKS = { in: :in? }
12
14
  NUMBER_CHECKS = { odd: :odd?, even: :even? }
13
15
 
14
- RESERVED_OPTIONS = COMPARE_CHECKS.keys + NUMBER_CHECKS.keys + RANGE_CHECKS.keys + [:only_integer]
16
+ RESERVED_OPTIONS = COMPARE_CHECKS.keys + NUMBER_CHECKS.keys + RANGE_CHECKS.keys + [:only_integer, :only_numeric]
15
17
 
16
18
  INTEGER_REGEX = /\A[+-]?\d+\z/
17
19
 
@@ -64,7 +66,7 @@ module ActiveModel
64
66
 
65
67
  private
66
68
  def option_as_number(record, option_value, precision, scale)
67
- parse_as_number(option_value(record, option_value), precision, scale)
69
+ parse_as_number(resolve_value(record, option_value), precision, scale)
68
70
  end
69
71
 
70
72
  def parse_as_number(raw_value, precision, scale)
@@ -90,6 +92,10 @@ module ActiveModel
90
92
  end
91
93
 
92
94
  def is_number?(raw_value, precision, scale)
95
+ if options[:only_numeric] && !raw_value.is_a?(Numeric)
96
+ return false
97
+ end
98
+
93
99
  !parse_as_number(raw_value, precision, scale).nil?
94
100
  rescue ArgumentError, TypeError
95
101
  false
@@ -110,14 +116,7 @@ module ActiveModel
110
116
  end
111
117
 
112
118
  def allow_only_integer?(record)
113
- case options[:only_integer]
114
- when Symbol
115
- record.send(options[:only_integer])
116
- when Proc
117
- options[:only_integer].call(record)
118
- else
119
- options[:only_integer]
120
- end
119
+ resolve_value(record, options[:only_integer])
121
120
  end
122
121
 
123
122
  def prepare_value_for_validation(value, record, attr_name)
@@ -149,10 +148,11 @@ module ActiveModel
149
148
 
150
149
  module HelperMethods
151
150
  # Validates whether the value of the specified attribute is numeric by
152
- # trying to convert it to a float with Kernel.Float (if <tt>only_integer</tt>
153
- # is +false+) or applying it to the regular expression <tt>/\A[\+\-]?\d+\z/</tt>
154
- # (if <tt>only_integer</tt> is set to +true+). Precision of Kernel.Float values
155
- # are guaranteed up to 15 digits.
151
+ # trying to convert it to a float with +Kernel.Float+ (if
152
+ # <tt>only_integer</tt> is +false+) or applying it to the regular
153
+ # expression <tt>/\A[\+\-]?\d+\z/</tt> (if <tt>only_integer</tt> is set to
154
+ # +true+). Precision of +Kernel.Float+ values are guaranteed up to 15
155
+ # digits.
156
156
  #
157
157
  # class Person < ActiveRecord::Base
158
158
  # validates_numericality_of :value, on: :create
@@ -162,24 +162,36 @@ module ActiveModel
162
162
  # * <tt>:message</tt> - A custom error message (default is: "is not a number").
163
163
  # * <tt>:only_integer</tt> - Specifies whether the value has to be an
164
164
  # integer (default is +false+).
165
+ # * <tt>:only_numeric</tt> - Specifies whether the value has to be an
166
+ # instance of Numeric (default is +false+). The default behavior is to
167
+ # attempt parsing the value if it is a String.
165
168
  # * <tt>:allow_nil</tt> - Skip validation if attribute is +nil+ (default is
166
169
  # +false+). Notice that for Integer and Float columns empty strings are
167
170
  # converted to +nil+.
168
171
  # * <tt>:greater_than</tt> - Specifies the value must be greater than the
169
- # supplied value.
172
+ # supplied value. The default error message for this option is _"must be
173
+ # greater than %{count}"_.
170
174
  # * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be
171
- # greater than or equal the supplied value.
175
+ # greater than or equal the supplied value. The default error message
176
+ # for this option is _"must be greater than or equal to %{count}"_.
172
177
  # * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied
173
- # value.
178
+ # value. The default error message for this option is _"must be equal to
179
+ # %{count}"_.
174
180
  # * <tt>:less_than</tt> - Specifies the value must be less than the
175
- # supplied value.
181
+ # supplied value. The default error message for this option is _"must be
182
+ # less than %{count}"_.
176
183
  # * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less
177
- # than or equal the supplied value.
184
+ # than or equal the supplied value. The default error message for this
185
+ # option is _"must be less than or equal to %{count}"_.
178
186
  # * <tt>:other_than</tt> - Specifies the value must be other than the
179
- # supplied value.
180
- # * <tt>:odd</tt> - Specifies the value must be an odd number.
181
- # * <tt>:even</tt> - Specifies the value must be an even number.
182
- # * <tt>:in</tt> - Check that the value is within a range.
187
+ # supplied value. The default error message for this option is _"must be
188
+ # other than %{count}"_.
189
+ # * <tt>:odd</tt> - Specifies the value must be an odd number. The default
190
+ # error message for this option is _"must be odd"_.
191
+ # * <tt>:even</tt> - Specifies the value must be an even number. The
192
+ # default error message for this option is _"must be even"_.
193
+ # * <tt>:in</tt> - Check that the value is within a range. The default
194
+ # error message for this option is _"must be in %{count}"_.
183
195
  #
184
196
  # There is also a list of default options supported by every validator:
185
197
  # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
@@ -10,7 +10,7 @@ module ActiveModel
10
10
 
11
11
  module HelperMethods
12
12
  # Validates that the specified attributes are not blank (as defined by
13
- # Object#blank?). Happens by default on save.
13
+ # Object#blank?).
14
14
  #
15
15
  # class Person < ActiveRecord::Base
16
16
  # validates_presence_of :first_name
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Validations
5
+ module ResolveValue # :nodoc:
6
+ def resolve_value(record, value)
7
+ case value
8
+ when Proc
9
+ if value.arity == 0
10
+ value.call
11
+ else
12
+ value.call(record)
13
+ end
14
+ when Symbol
15
+ record.send(value)
16
+ else
17
+ if value.respond_to?(:call)
18
+ value.call(record)
19
+ else
20
+ value
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -6,7 +6,7 @@ module ActiveModel
6
6
  module Validations
7
7
  module ClassMethods
8
8
  # This method is a shortcut to all default validators and any custom
9
- # validator classes ending in 'Validator'. Note that Rails default
9
+ # validator classes ending in 'Validator'. Note that \Rails default
10
10
  # validators can be overridden inside specific classes by creating
11
11
  # custom validator classes in their place such as PresenceValidator.
12
12
  #
@@ -116,7 +116,7 @@ module ActiveModel
116
116
  key = "#{key.to_s.camelize}Validator"
117
117
 
118
118
  begin
119
- validator = key.include?("::") ? key.constantize : const_get(key)
119
+ validator = const_get(key)
120
120
  rescue NameError
121
121
  raise ArgumentError, "Unknown validator: '#{key}'"
122
122
  end
@@ -130,7 +130,7 @@ module ActiveModel
130
130
  # This method is used to define validations that cannot be corrected by end
131
131
  # users and are considered exceptional. So each validator defined with bang
132
132
  # or <tt>:strict</tt> option set to <tt>true</tt> will always raise
133
- # <tt>ActiveModel::StrictValidationFailed</tt> instead of adding error
133
+ # ActiveModel::StrictValidationFailed instead of adding error
134
134
  # when validation fails. See <tt>validates</tt> for more information about
135
135
  # the validation itself.
136
136
  #
@@ -45,6 +45,13 @@ module ActiveModel
45
45
  # validates_with MyValidator, MyOtherValidator, on: :create
46
46
  # end
47
47
  #
48
+ # There is no default error message for +validates_with+. You must
49
+ # manually add errors to the record's errors collection in the validator
50
+ # class.
51
+ #
52
+ # To implement the validate method, you must have a +record+ parameter
53
+ # defined, which is the record to be validated.
54
+ #
48
55
  # Configuration options:
49
56
  # * <tt>:on</tt> - Specifies the contexts where this validation is active.
50
57
  # Runs in all validation contexts by default +nil+. You can pass a symbol
@@ -83,7 +90,7 @@ module ActiveModel
83
90
  options[:class] = self
84
91
 
85
92
  args.each do |klass|
86
- validator = klass.new(options, &block)
93
+ validator = klass.new(options.dup, &block)
87
94
 
88
95
  if validator.respond_to?(:attributes) && !validator.attributes.empty?
89
96
  validator.attributes.each do |attribute|
@@ -139,7 +146,7 @@ module ActiveModel
139
146
  options[:class] = self.class
140
147
 
141
148
  args.each do |klass|
142
- validator = klass.new(options, &block)
149
+ validator = klass.new(options.dup, &block)
143
150
  validator.validate(self)
144
151
  end
145
152
  end