activemodel 4.2.0 → 6.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 (71) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +49 -37
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +16 -22
  5. data/lib/active_model/attribute/user_provided_default.rb +51 -0
  6. data/lib/active_model/attribute.rb +248 -0
  7. data/lib/active_model/attribute_assignment.rb +55 -0
  8. data/lib/active_model/attribute_methods.rb +150 -73
  9. data/lib/active_model/attribute_mutation_tracker.rb +181 -0
  10. data/lib/active_model/attribute_set/builder.rb +191 -0
  11. data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
  12. data/lib/active_model/attribute_set.rb +106 -0
  13. data/lib/active_model/attributes.rb +132 -0
  14. data/lib/active_model/callbacks.rb +31 -25
  15. data/lib/active_model/conversion.rb +20 -9
  16. data/lib/active_model/dirty.rb +142 -116
  17. data/lib/active_model/error.rb +207 -0
  18. data/lib/active_model/errors.rb +436 -202
  19. data/lib/active_model/forbidden_attributes_protection.rb +6 -3
  20. data/lib/active_model/gem_version.rb +5 -3
  21. data/lib/active_model/lint.rb +47 -42
  22. data/lib/active_model/locale/en.yml +2 -1
  23. data/lib/active_model/model.rb +7 -7
  24. data/lib/active_model/naming.rb +36 -18
  25. data/lib/active_model/nested_error.rb +22 -0
  26. data/lib/active_model/railtie.rb +8 -0
  27. data/lib/active_model/secure_password.rb +61 -67
  28. data/lib/active_model/serialization.rb +48 -17
  29. data/lib/active_model/serializers/json.rb +22 -13
  30. data/lib/active_model/translation.rb +5 -4
  31. data/lib/active_model/type/big_integer.rb +14 -0
  32. data/lib/active_model/type/binary.rb +52 -0
  33. data/lib/active_model/type/boolean.rb +46 -0
  34. data/lib/active_model/type/date.rb +52 -0
  35. data/lib/active_model/type/date_time.rb +46 -0
  36. data/lib/active_model/type/decimal.rb +69 -0
  37. data/lib/active_model/type/float.rb +35 -0
  38. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +49 -0
  39. data/lib/active_model/type/helpers/mutable.rb +20 -0
  40. data/lib/active_model/type/helpers/numeric.rb +48 -0
  41. data/lib/active_model/type/helpers/time_value.rb +90 -0
  42. data/lib/active_model/type/helpers/timezone.rb +19 -0
  43. data/lib/active_model/type/helpers.rb +7 -0
  44. data/lib/active_model/type/immutable_string.rb +35 -0
  45. data/lib/active_model/type/integer.rb +67 -0
  46. data/lib/active_model/type/registry.rb +70 -0
  47. data/lib/active_model/type/string.rb +35 -0
  48. data/lib/active_model/type/time.rb +46 -0
  49. data/lib/active_model/type/value.rb +133 -0
  50. data/lib/active_model/type.rb +53 -0
  51. data/lib/active_model/validations/absence.rb +6 -4
  52. data/lib/active_model/validations/acceptance.rb +72 -14
  53. data/lib/active_model/validations/callbacks.rb +23 -19
  54. data/lib/active_model/validations/clusivity.rb +18 -12
  55. data/lib/active_model/validations/confirmation.rb +27 -14
  56. data/lib/active_model/validations/exclusion.rb +7 -4
  57. data/lib/active_model/validations/format.rb +27 -27
  58. data/lib/active_model/validations/helper_methods.rb +15 -0
  59. data/lib/active_model/validations/inclusion.rb +8 -7
  60. data/lib/active_model/validations/length.rb +35 -32
  61. data/lib/active_model/validations/numericality.rb +72 -34
  62. data/lib/active_model/validations/presence.rb +3 -3
  63. data/lib/active_model/validations/validates.rb +17 -15
  64. data/lib/active_model/validations/with.rb +6 -12
  65. data/lib/active_model/validations.rb +58 -23
  66. data/lib/active_model/validator.rb +23 -17
  67. data/lib/active_model/version.rb +4 -2
  68. data/lib/active_model.rb +18 -11
  69. metadata +44 -25
  70. data/lib/active_model/serializers/xml.rb +0 -238
  71. data/lib/active_model/test_case.rb +0 -4
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ module Helpers # :nodoc: all
6
+ module Numeric
7
+ def serialize(value)
8
+ cast(value)
9
+ end
10
+
11
+ def cast(value)
12
+ # Checks whether the value is numeric. Spaceship operator
13
+ # will return nil if value is not numeric.
14
+ value = if value <=> 0
15
+ value
16
+ else
17
+ case value
18
+ when true then 1
19
+ when false then 0
20
+ else value.presence
21
+ end
22
+ end
23
+
24
+ super(value)
25
+ end
26
+
27
+ def changed?(old_value, _new_value, new_value_before_type_cast) # :nodoc:
28
+ super || number_to_non_number?(old_value, new_value_before_type_cast)
29
+ end
30
+
31
+ private
32
+ def number_to_non_number?(old_value, new_value_before_type_cast)
33
+ old_value != nil && non_numeric_string?(new_value_before_type_cast.to_s)
34
+ end
35
+
36
+ def non_numeric_string?(value)
37
+ # 'wibble'.to_i will give zero, we want to make sure
38
+ # that we aren't marking int zero to string zero as
39
+ # changed.
40
+ !NUMERIC_REGEX.match?(value)
41
+ end
42
+
43
+ NUMERIC_REGEX = /\A\s*[+-]?\d/
44
+ private_constant :NUMERIC_REGEX
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/string/zones"
4
+ require "active_support/core_ext/time/zones"
5
+
6
+ module ActiveModel
7
+ module Type
8
+ module Helpers # :nodoc: all
9
+ module TimeValue
10
+ def serialize(value)
11
+ value = apply_seconds_precision(value)
12
+
13
+ if value.acts_like?(:time)
14
+ if is_utc?
15
+ value = value.getutc if value.respond_to?(:getutc) && !value.utc?
16
+ else
17
+ value = value.getlocal if value.respond_to?(:getlocal)
18
+ end
19
+ end
20
+
21
+ value
22
+ end
23
+
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
30
+
31
+ if rounded_off_nsec > 0
32
+ value.change(nsec: value.nsec - rounded_off_nsec)
33
+ else
34
+ value
35
+ end
36
+ end
37
+
38
+ def type_cast_for_schema(value)
39
+ value.to_s(:db).inspect
40
+ end
41
+
42
+ def user_input_in_time_zone(value)
43
+ value.in_time_zone
44
+ end
45
+
46
+ private
47
+ def new_time(year, mon, mday, hour, min, sec, microsec, offset = nil)
48
+ # Treat 0000-00-00 00:00:00 as nil.
49
+ return if year.nil? || (year == 0 && mon == 0 && mday == 0)
50
+
51
+ if offset
52
+ time = ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
53
+ return unless time
54
+
55
+ time -= offset unless offset == 0
56
+ is_utc? ? time : time.getlocal
57
+ elsif is_utc?
58
+ ::Time.utc(year, mon, mday, hour, min, sec, microsec) rescue nil
59
+ else
60
+ ::Time.local(year, mon, mday, hour, min, sec, microsec) rescue nil
61
+ end
62
+ end
63
+
64
+ ISO_DATETIME = /
65
+ \A
66
+ (\d{4})-(\d\d)-(\d\d)(?:T|\s) # 2020-06-20T
67
+ (\d\d):(\d\d):(\d\d)(?:\.(\d{1,6})\d*)? # 10:20:30.123456
68
+ (?:(Z(?=\z)|[+-]\d\d)(?::?(\d\d))?)? # +09:00
69
+ \z
70
+ /x
71
+
72
+ def fast_string_to_time(string)
73
+ return unless ISO_DATETIME =~ string
74
+
75
+ usec = $7.to_i
76
+ usec_len = $7&.length
77
+ if usec_len&.< 6
78
+ usec *= 10**(6 - usec_len)
79
+ end
80
+
81
+ if $8
82
+ offset = $8 == "Z" ? 0 : $8.to_i * 3600 + $9.to_i * 60
83
+ end
84
+
85
+ new_time($1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, $6.to_i, usec, offset)
86
+ end
87
+ end
88
+ end
89
+ end
90
+ 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.match?("UTC")
11
+ end
12
+
13
+ def default_timezone
14
+ is_utc? ? :utc : :local
15
+ end
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/type/helpers/accepts_multiparameter_time"
4
+ require "active_model/type/helpers/numeric"
5
+ require "active_model/type/helpers/mutable"
6
+ require "active_model/type/helpers/time_value"
7
+ require "active_model/type/helpers/timezone"
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ class ImmutableString < Value # :nodoc:
6
+ def initialize(**args)
7
+ @true = -(args.delete(:true)&.to_s || "t")
8
+ @false = -(args.delete(:false)&.to_s || "f")
9
+ super
10
+ end
11
+
12
+ def type
13
+ :string
14
+ end
15
+
16
+ def serialize(value)
17
+ case value
18
+ when ::Numeric, ::Symbol, ActiveSupport::Duration then value.to_s
19
+ when true then @true
20
+ when false then @false
21
+ else super
22
+ end
23
+ end
24
+
25
+ private
26
+ def cast_value(value)
27
+ case value
28
+ when true then @true
29
+ when false then @false
30
+ else value.to_s.freeze
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ class Integer < Value # :nodoc:
6
+ include Helpers::Numeric
7
+
8
+ # Column storage size in bytes.
9
+ # 4 bytes means an integer as opposed to smallint etc.
10
+ DEFAULT_LIMIT = 4
11
+
12
+ def initialize(**)
13
+ super
14
+ @range = min_value...max_value
15
+ end
16
+
17
+ def type
18
+ :integer
19
+ end
20
+
21
+ def deserialize(value)
22
+ return if value.blank?
23
+ value.to_i
24
+ end
25
+
26
+ def serialize(value)
27
+ return if value.is_a?(::String) && non_numeric_string?(value)
28
+ ensure_in_range(super)
29
+ end
30
+
31
+ def serializable?(value)
32
+ cast_value = cast(value)
33
+ in_range?(cast_value) && super
34
+ end
35
+
36
+ private
37
+ attr_reader :range
38
+
39
+ def in_range?(value)
40
+ !value || range.member?(value)
41
+ end
42
+
43
+ def cast_value(value)
44
+ value.to_i rescue nil
45
+ end
46
+
47
+ def ensure_in_range(value)
48
+ unless in_range?(value)
49
+ raise ActiveModel::RangeError, "#{value} is out of range for #{self.class} with limit #{_limit} bytes"
50
+ end
51
+ value
52
+ end
53
+
54
+ def max_value
55
+ 1 << (_limit * 8 - 1) # 8 bits per byte with one bit for sign
56
+ end
57
+
58
+ def min_value
59
+ -max_value
60
+ end
61
+
62
+ def _limit
63
+ limit || DEFAULT_LIMIT
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ # :stopdoc:
5
+ module Type
6
+ class Registry
7
+ def initialize
8
+ @registrations = []
9
+ end
10
+
11
+ def initialize_dup(other)
12
+ @registrations = @registrations.dup
13
+ super
14
+ end
15
+
16
+ def register(type_name, klass = nil, **options, &block)
17
+ unless block_given?
18
+ block = proc { |_, *args| klass.new(*args) }
19
+ block.ruby2_keywords if block.respond_to?(:ruby2_keywords)
20
+ end
21
+ registrations << registration_klass.new(type_name, block, **options)
22
+ end
23
+
24
+ def lookup(symbol, *args, **kwargs)
25
+ registration = find_registration(symbol, *args, **kwargs)
26
+
27
+ if registration
28
+ registration.call(self, symbol, *args, **kwargs)
29
+ else
30
+ raise ArgumentError, "Unknown type #{symbol.inspect}"
31
+ end
32
+ end
33
+
34
+ private
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
+ end
68
+ end
69
+ # :startdoc:
70
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/type/immutable_string"
4
+
5
+ module ActiveModel
6
+ module Type
7
+ class String < ImmutableString # :nodoc:
8
+ def changed_in_place?(raw_old_value, new_value)
9
+ if new_value.is_a?(::String)
10
+ raw_old_value != new_value
11
+ end
12
+ end
13
+
14
+ def to_immutable_string
15
+ ImmutableString.new(
16
+ true: @true,
17
+ false: @false,
18
+ limit: limit,
19
+ precision: precision,
20
+ scale: scale,
21
+ )
22
+ end
23
+
24
+ private
25
+ def cast_value(value)
26
+ case value
27
+ when ::String then ::String.new(value)
28
+ when true then @true
29
+ when false then @false
30
+ else value.to_s
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ class Time < Value # :nodoc:
6
+ include Helpers::Timezone
7
+ include Helpers::TimeValue
8
+ include Helpers::AcceptsMultiparameterTime.new(
9
+ defaults: { 1 => 2000, 2 => 1, 3 => 1, 4 => 0, 5 => 0 }
10
+ )
11
+
12
+ def type
13
+ :time
14
+ end
15
+
16
+ def user_input_in_time_zone(value)
17
+ return unless value.present?
18
+
19
+ case value
20
+ when ::String
21
+ value = "2000-01-01 #{value}"
22
+ time_hash = ::Date._parse(value)
23
+ return if time_hash[:hour].nil?
24
+ when ::Time
25
+ value = value.change(year: 2000, day: 1, month: 1)
26
+ end
27
+
28
+ super(value)
29
+ end
30
+
31
+ private
32
+ def cast_value(value)
33
+ return apply_seconds_precision(value) unless value.is_a?(::String)
34
+ return if value.empty?
35
+
36
+ dummy_time_value = value.sub(/\A(\d\d\d\d-\d\d-\d\d |)/, "2000-01-01 ")
37
+
38
+ fast_string_to_time(dummy_time_value) || begin
39
+ time_hash = ::Date._parse(dummy_time_value)
40
+ return if time_hash[:hour].nil?
41
+ new_time(*time_hash.values_at(:year, :mon, :mday, :hour, :min, :sec, :sec_fraction, :offset))
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Type
5
+ class Value
6
+ attr_reader :precision, :scale, :limit
7
+
8
+ def initialize(precision: nil, limit: nil, scale: nil)
9
+ @precision = precision
10
+ @scale = scale
11
+ @limit = limit
12
+ end
13
+
14
+ # Returns true if this type can convert +value+ to a type that is usable
15
+ # by the database. For example a boolean type can return +true+ if the
16
+ # value parameter is a Ruby boolean, but may return +false+ if the value
17
+ # parameter is some other object.
18
+ def serializable?(value)
19
+ true
20
+ end
21
+
22
+ def type # :nodoc:
23
+ end
24
+
25
+ # Converts a value from database input to the appropriate ruby type. The
26
+ # return value of this method will be returned from
27
+ # ActiveRecord::AttributeMethods::Read#read_attribute. The default
28
+ # implementation just calls Value#cast.
29
+ #
30
+ # +value+ The raw input, as provided from the database.
31
+ def deserialize(value)
32
+ cast(value)
33
+ end
34
+
35
+ # Type casts a value from user input (e.g. from a setter). This value may
36
+ # be a string from the form builder, or a ruby object passed to a setter.
37
+ # There is currently no way to differentiate between which source it came
38
+ # from.
39
+ #
40
+ # The return value of this method will be returned from
41
+ # ActiveRecord::AttributeMethods::Read#read_attribute. See also:
42
+ # Value#cast_value.
43
+ #
44
+ # +value+ The raw input, as provided to the attribute setter.
45
+ def cast(value)
46
+ cast_value(value) unless value.nil?
47
+ end
48
+
49
+ # Casts a value from the ruby type to a type that the database knows how
50
+ # to understand. The returned value from this method should be a
51
+ # +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or
52
+ # +nil+.
53
+ def serialize(value)
54
+ value
55
+ end
56
+
57
+ # Type casts a value for schema dumping. This method is private, as we are
58
+ # hoping to remove it entirely.
59
+ def type_cast_for_schema(value) # :nodoc:
60
+ value.inspect
61
+ end
62
+
63
+ # These predicates are not documented, as I need to look further into
64
+ # their use, and see if they can be removed entirely.
65
+ def binary? # :nodoc:
66
+ false
67
+ end
68
+
69
+ # Determines whether a value has changed for dirty checking. +old_value+
70
+ # and +new_value+ will always be type-cast. Types should not need to
71
+ # override this method.
72
+ def changed?(old_value, new_value, _new_value_before_type_cast)
73
+ old_value != new_value
74
+ end
75
+
76
+ # Determines whether the mutable value has been modified since it was
77
+ # read. Returns +false+ by default. If your type returns an object
78
+ # which could be mutated, you should override this method. You will need
79
+ # to either:
80
+ #
81
+ # - pass +new_value+ to Value#serialize and compare it to
82
+ # +raw_old_value+
83
+ #
84
+ # or
85
+ #
86
+ # - pass +raw_old_value+ to Value#deserialize and compare it to
87
+ # +new_value+
88
+ #
89
+ # +raw_old_value+ The original value, before being passed to
90
+ # +deserialize+.
91
+ #
92
+ # +new_value+ The current value, after type casting.
93
+ def changed_in_place?(raw_old_value, new_value)
94
+ false
95
+ end
96
+
97
+ def value_constructed_by_mass_assignment?(_value) # :nodoc:
98
+ false
99
+ end
100
+
101
+ def force_equality?(_value) # :nodoc:
102
+ false
103
+ end
104
+
105
+ def map(value) # :nodoc:
106
+ yield value
107
+ end
108
+
109
+ def ==(other)
110
+ self.class == other.class &&
111
+ precision == other.precision &&
112
+ scale == other.scale &&
113
+ limit == other.limit
114
+ end
115
+ alias eql? ==
116
+
117
+ def hash
118
+ [self.class, precision, scale, limit].hash
119
+ end
120
+
121
+ def assert_valid_value(_)
122
+ end
123
+
124
+ private
125
+ # Convenience method for types which do not need separate type casting
126
+ # behavior for user and database inputs. Called by Value#cast for
127
+ # values except +nil+.
128
+ def cast_value(value) # :doc:
129
+ value
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/type/helpers"
4
+ require "active_model/type/value"
5
+
6
+ require "active_model/type/big_integer"
7
+ require "active_model/type/binary"
8
+ require "active_model/type/boolean"
9
+ require "active_model/type/date"
10
+ require "active_model/type/date_time"
11
+ require "active_model/type/decimal"
12
+ require "active_model/type/float"
13
+ require "active_model/type/immutable_string"
14
+ require "active_model/type/integer"
15
+ require "active_model/type/string"
16
+ require "active_model/type/time"
17
+
18
+ require "active_model/type/registry"
19
+
20
+ module ActiveModel
21
+ module Type
22
+ @registry = Registry.new
23
+
24
+ class << self
25
+ attr_accessor :registry # :nodoc:
26
+
27
+ # Add a new type to the registry, allowing it to be gotten through ActiveModel::Type#lookup
28
+ def register(type_name, klass = nil, **options, &block)
29
+ registry.register(type_name, klass, **options, &block)
30
+ end
31
+
32
+ def lookup(*args, **kwargs) # :nodoc:
33
+ registry.lookup(*args, **kwargs)
34
+ end
35
+
36
+ def default_value # :nodoc:
37
+ @default_value ||= Value.new
38
+ end
39
+ end
40
+
41
+ register(:big_integer, Type::BigInteger)
42
+ register(:binary, Type::Binary)
43
+ register(:boolean, Type::Boolean)
44
+ register(:date, Type::Date)
45
+ register(:datetime, Type::DateTime)
46
+ register(:decimal, Type::Decimal)
47
+ register(:float, Type::Float)
48
+ register(:immutable_string, Type::ImmutableString)
49
+ register(:integer, Type::Integer)
50
+ register(:string, Type::String)
51
+ register(:time, Type::Time)
52
+ end
53
+ end
@@ -1,15 +1,17 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module ActiveModel
2
4
  module Validations
3
- # == Active Model Absence Validator
5
+ # == \Active \Model Absence Validator
4
6
  class AbsenceValidator < EachValidator #:nodoc:
5
7
  def validate_each(record, attr_name, value)
6
- record.errors.add(attr_name, :present, options) if value.present?
8
+ record.errors.add(attr_name, :present, **options) if value.present?
7
9
  end
8
10
  end
9
11
 
10
12
  module HelperMethods
11
13
  # Validates that the specified attributes are blank (as defined by
12
- # Object#blank?). Happens by default on save.
14
+ # Object#present?). Happens by default on save.
13
15
  #
14
16
  # class Person < ActiveRecord::Base
15
17
  # validates_absence_of :first_name
@@ -22,7 +24,7 @@ module ActiveModel
22
24
  #
23
25
  # There is also a list of default options supported by every validator:
24
26
  # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
25
- # See <tt>ActiveModel::Validation#validates</tt> for more information
27
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
26
28
  def validates_absence_of(*attr_names)
27
29
  validates_with AbsenceValidator, _merge_attributes(attr_names)
28
30
  end