omg-activemodel 8.0.0.alpha1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (77) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +67 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +266 -0
  5. data/lib/active_model/access.rb +16 -0
  6. data/lib/active_model/api.rb +99 -0
  7. data/lib/active_model/attribute/user_provided_default.rb +55 -0
  8. data/lib/active_model/attribute.rb +277 -0
  9. data/lib/active_model/attribute_assignment.rb +78 -0
  10. data/lib/active_model/attribute_methods.rb +592 -0
  11. data/lib/active_model/attribute_mutation_tracker.rb +189 -0
  12. data/lib/active_model/attribute_registration.rb +117 -0
  13. data/lib/active_model/attribute_set/builder.rb +182 -0
  14. data/lib/active_model/attribute_set/yaml_encoder.rb +40 -0
  15. data/lib/active_model/attribute_set.rb +118 -0
  16. data/lib/active_model/attributes.rb +165 -0
  17. data/lib/active_model/callbacks.rb +155 -0
  18. data/lib/active_model/conversion.rb +121 -0
  19. data/lib/active_model/deprecator.rb +7 -0
  20. data/lib/active_model/dirty.rb +416 -0
  21. data/lib/active_model/error.rb +208 -0
  22. data/lib/active_model/errors.rb +547 -0
  23. data/lib/active_model/forbidden_attributes_protection.rb +33 -0
  24. data/lib/active_model/gem_version.rb +17 -0
  25. data/lib/active_model/lint.rb +118 -0
  26. data/lib/active_model/locale/en.yml +38 -0
  27. data/lib/active_model/model.rb +78 -0
  28. data/lib/active_model/naming.rb +359 -0
  29. data/lib/active_model/nested_error.rb +22 -0
  30. data/lib/active_model/railtie.rb +24 -0
  31. data/lib/active_model/secure_password.rb +231 -0
  32. data/lib/active_model/serialization.rb +198 -0
  33. data/lib/active_model/serializers/json.rb +154 -0
  34. data/lib/active_model/translation.rb +78 -0
  35. data/lib/active_model/type/big_integer.rb +36 -0
  36. data/lib/active_model/type/binary.rb +62 -0
  37. data/lib/active_model/type/boolean.rb +48 -0
  38. data/lib/active_model/type/date.rb +78 -0
  39. data/lib/active_model/type/date_time.rb +88 -0
  40. data/lib/active_model/type/decimal.rb +107 -0
  41. data/lib/active_model/type/float.rb +64 -0
  42. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +53 -0
  43. data/lib/active_model/type/helpers/mutable.rb +24 -0
  44. data/lib/active_model/type/helpers/numeric.rb +61 -0
  45. data/lib/active_model/type/helpers/time_value.rb +127 -0
  46. data/lib/active_model/type/helpers/timezone.rb +23 -0
  47. data/lib/active_model/type/helpers.rb +7 -0
  48. data/lib/active_model/type/immutable_string.rb +71 -0
  49. data/lib/active_model/type/integer.rb +113 -0
  50. data/lib/active_model/type/registry.rb +37 -0
  51. data/lib/active_model/type/serialize_cast_value.rb +47 -0
  52. data/lib/active_model/type/string.rb +43 -0
  53. data/lib/active_model/type/time.rb +87 -0
  54. data/lib/active_model/type/value.rb +157 -0
  55. data/lib/active_model/type.rb +55 -0
  56. data/lib/active_model/validations/absence.rb +33 -0
  57. data/lib/active_model/validations/acceptance.rb +113 -0
  58. data/lib/active_model/validations/callbacks.rb +119 -0
  59. data/lib/active_model/validations/clusivity.rb +54 -0
  60. data/lib/active_model/validations/comparability.rb +18 -0
  61. data/lib/active_model/validations/comparison.rb +90 -0
  62. data/lib/active_model/validations/confirmation.rb +80 -0
  63. data/lib/active_model/validations/exclusion.rb +49 -0
  64. data/lib/active_model/validations/format.rb +112 -0
  65. data/lib/active_model/validations/helper_methods.rb +15 -0
  66. data/lib/active_model/validations/inclusion.rb +47 -0
  67. data/lib/active_model/validations/length.rb +130 -0
  68. data/lib/active_model/validations/numericality.rb +222 -0
  69. data/lib/active_model/validations/presence.rb +39 -0
  70. data/lib/active_model/validations/resolve_value.rb +26 -0
  71. data/lib/active_model/validations/validates.rb +175 -0
  72. data/lib/active_model/validations/with.rb +154 -0
  73. data/lib/active_model/validations.rb +489 -0
  74. data/lib/active_model/validator.rb +190 -0
  75. data/lib/active_model/version.rb +10 -0
  76. data/lib/active_model.rb +84 -0
  77. metadata +139 -0
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
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.
9
+ class Value
10
+ include SerializeCastValue
11
+ attr_reader :precision, :scale, :limit
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.
17
+ def initialize(precision: nil, limit: nil, scale: nil)
18
+ super()
19
+ @precision = precision
20
+ @scale = scale
21
+ @limit = limit
22
+ end
23
+
24
+ # Returns true if this type can convert +value+ to a type that is usable
25
+ # by the database. For example a boolean type can return +true+ if the
26
+ # value parameter is a Ruby boolean, but may return +false+ if the value
27
+ # parameter is some other object.
28
+ def serializable?(value)
29
+ true
30
+ end
31
+
32
+ # Returns the unique type name as a Symbol. Subclasses should override
33
+ # this method.
34
+ def type
35
+ end
36
+
37
+ # Converts a value from database input to the appropriate ruby type. The
38
+ # return value of this method will be returned from
39
+ # ActiveRecord::AttributeMethods::Read#read_attribute. The default
40
+ # implementation just calls Value#cast.
41
+ #
42
+ # +value+ The raw input, as provided from the database.
43
+ def deserialize(value)
44
+ cast(value)
45
+ end
46
+
47
+ # Type casts a value from user input (e.g. from a setter). This value may
48
+ # be a string from the form builder, or a ruby object passed to a setter.
49
+ # There is currently no way to differentiate between which source it came
50
+ # from.
51
+ #
52
+ # The return value of this method will be returned from
53
+ # ActiveRecord::AttributeMethods::Read#read_attribute. See also:
54
+ # Value#cast_value.
55
+ #
56
+ # +value+ The raw input, as provided to the attribute setter.
57
+ def cast(value)
58
+ cast_value(value) unless value.nil?
59
+ end
60
+
61
+ # Casts a value from the ruby type to a type that the database knows how
62
+ # to understand. The returned value from this method should be a
63
+ # +String+, +Numeric+, +Date+, +Time+, +Symbol+, +true+, +false+, or
64
+ # +nil+.
65
+ def serialize(value)
66
+ value
67
+ end
68
+
69
+ # Type casts a value for schema dumping. This method is private, as we are
70
+ # hoping to remove it entirely.
71
+ def type_cast_for_schema(value) # :nodoc:
72
+ value.inspect
73
+ end
74
+
75
+ # These predicates are not documented, as I need to look further into
76
+ # their use, and see if they can be removed entirely.
77
+ def binary? # :nodoc:
78
+ false
79
+ end
80
+
81
+ # Determines whether a value has changed for dirty checking. +old_value+
82
+ # and +new_value+ will always be type-cast. Types should not need to
83
+ # override this method.
84
+ def changed?(old_value, new_value, _new_value_before_type_cast)
85
+ old_value != new_value
86
+ end
87
+
88
+ # Determines whether the mutable value has been modified since it was
89
+ # read. Returns +false+ by default. If your type returns an object
90
+ # which could be mutated, you should override this method. You will need
91
+ # to either:
92
+ #
93
+ # - pass +new_value+ to Value#serialize and compare it to
94
+ # +raw_old_value+
95
+ #
96
+ # or
97
+ #
98
+ # - pass +raw_old_value+ to Value#deserialize and compare it to
99
+ # +new_value+
100
+ #
101
+ # +raw_old_value+ The original value, before being passed to
102
+ # +deserialize+.
103
+ #
104
+ # +new_value+ The current value, after type casting.
105
+ def changed_in_place?(raw_old_value, new_value)
106
+ false
107
+ end
108
+
109
+ def value_constructed_by_mass_assignment?(_value) # :nodoc:
110
+ false
111
+ end
112
+
113
+ def force_equality?(_value) # :nodoc:
114
+ false
115
+ end
116
+
117
+ def map(value) # :nodoc:
118
+ yield value
119
+ end
120
+
121
+ def ==(other)
122
+ self.class == other.class &&
123
+ precision == other.precision &&
124
+ scale == other.scale &&
125
+ limit == other.limit
126
+ end
127
+ alias eql? ==
128
+
129
+ def hash
130
+ [self.class, precision, scale, limit].hash
131
+ end
132
+
133
+ def assert_valid_value(_)
134
+ end
135
+
136
+ def serialized? # :nodoc:
137
+ false
138
+ end
139
+
140
+ def mutable? # :nodoc:
141
+ false
142
+ end
143
+
144
+ def as_json(*)
145
+ raise NoMethodError
146
+ end
147
+
148
+ private
149
+ # Convenience method for types which do not need separate type casting
150
+ # behavior for user and database inputs. Called by Value#cast for
151
+ # values except +nil+.
152
+ def cast_value(value) # :doc:
153
+ value
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,55 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/type/helpers"
4
+ require "active_model/type/serialize_cast_value"
5
+ require "active_model/type/value"
6
+
7
+ require "active_model/type/big_integer"
8
+ require "active_model/type/binary"
9
+ require "active_model/type/boolean"
10
+ require "active_model/type/date"
11
+ require "active_model/type/date_time"
12
+ require "active_model/type/decimal"
13
+ require "active_model/type/float"
14
+ require "active_model/type/immutable_string"
15
+ require "active_model/type/integer"
16
+ require "active_model/type/string"
17
+ require "active_model/type/time"
18
+
19
+ require "active_model/type/registry"
20
+
21
+ module ActiveModel
22
+ module Type
23
+ @registry = Registry.new
24
+
25
+ class << self
26
+ attr_accessor :registry # :nodoc:
27
+
28
+ # Add a new type to the registry, allowing it to be referenced as a
29
+ # symbol by {attribute}[rdoc-ref:Attributes::ClassMethods#attribute].
30
+ def register(type_name, klass = nil, &block)
31
+ registry.register(type_name, klass, &block)
32
+ end
33
+
34
+ def lookup(...) # :nodoc:
35
+ registry.lookup(...)
36
+ end
37
+
38
+ def default_value # :nodoc:
39
+ @default_value ||= Value.new
40
+ end
41
+ end
42
+
43
+ register(:big_integer, Type::BigInteger)
44
+ register(:binary, Type::Binary)
45
+ register(:boolean, Type::Boolean)
46
+ register(:date, Type::Date)
47
+ register(:datetime, Type::DateTime)
48
+ register(:decimal, Type::Decimal)
49
+ register(:float, Type::Float)
50
+ register(:immutable_string, Type::ImmutableString)
51
+ register(:integer, Type::Integer)
52
+ register(:string, Type::String)
53
+ register(:time, Type::Time)
54
+ end
55
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Validations
5
+ # == \Active \Model Absence Validator
6
+ class AbsenceValidator < EachValidator # :nodoc:
7
+ def validate_each(record, attr_name, value)
8
+ record.errors.add(attr_name, :present, **options) if value.present?
9
+ end
10
+ end
11
+
12
+ module HelperMethods
13
+ # Validates that the specified attributes are blank (as defined by
14
+ # Object#present?).
15
+ #
16
+ # class Person < ActiveRecord::Base
17
+ # validates_absence_of :first_name
18
+ # end
19
+ #
20
+ # The first_name attribute must be in the object and it must be blank.
21
+ #
22
+ # Configuration options:
23
+ # * <tt>:message</tt> - A custom error message (default is: "must be blank").
24
+ #
25
+ # There is also a list of default options supported by every validator:
26
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
27
+ # See ActiveModel::Validations::ClassMethods#validates for more information.
28
+ def validates_absence_of(*attr_names)
29
+ validates_with AbsenceValidator, _merge_attributes(attr_names)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,113 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Validations
5
+ class AcceptanceValidator < EachValidator # :nodoc:
6
+ def initialize(options)
7
+ super({ allow_nil: true, accept: ["1", true] }.merge!(options))
8
+ setup!(options[:class])
9
+ end
10
+
11
+ def validate_each(record, attribute, value)
12
+ unless acceptable_option?(value)
13
+ record.errors.add(attribute, :accepted, **options.except(:accept, :allow_nil))
14
+ end
15
+ end
16
+
17
+ private
18
+ def setup!(klass)
19
+ define_attributes = LazilyDefineAttributes.new(attributes)
20
+ klass.include(define_attributes) unless klass.included_modules.include?(define_attributes)
21
+ end
22
+
23
+ def acceptable_option?(value)
24
+ Array(options[:accept]).include?(value)
25
+ end
26
+
27
+ class LazilyDefineAttributes < Module
28
+ def initialize(attributes)
29
+ @attributes = attributes.map(&:to_s)
30
+ end
31
+
32
+ def included(klass)
33
+ @lock = Mutex.new
34
+ mod = self
35
+
36
+ define_method(:respond_to_missing?) do |method_name, include_private = false|
37
+ mod.define_on(klass)
38
+ super(method_name, include_private) || mod.matches?(method_name)
39
+ end
40
+
41
+ define_method(:method_missing) do |method_name, *args, &block|
42
+ mod.define_on(klass)
43
+ if mod.matches?(method_name)
44
+ send(method_name, *args, &block)
45
+ else
46
+ super(method_name, *args, &block)
47
+ end
48
+ end
49
+ end
50
+
51
+ def matches?(method_name)
52
+ attr_name = method_name.to_s.chomp("=")
53
+ attributes.any? { |name| name == attr_name }
54
+ end
55
+
56
+ def define_on(klass)
57
+ @lock&.synchronize do
58
+ return unless @lock
59
+
60
+ attr_readers = attributes.reject { |name| klass.attribute_method?(name) }
61
+ attr_writers = attributes.reject { |name| klass.attribute_method?("#{name}=") }
62
+
63
+ attr_reader(*attr_readers)
64
+ attr_writer(*attr_writers)
65
+
66
+ remove_method :respond_to_missing?
67
+ remove_method :method_missing
68
+
69
+ @lock = nil
70
+ end
71
+ end
72
+
73
+ def ==(other)
74
+ self.class == other.class && attributes == other.attributes
75
+ end
76
+
77
+ protected
78
+ attr_reader :attributes
79
+ end
80
+ end
81
+
82
+ module HelperMethods
83
+ # Encapsulates the pattern of wanting to validate the acceptance of a
84
+ # terms of service check box (or similar agreement).
85
+ #
86
+ # class Person < ActiveRecord::Base
87
+ # validates_acceptance_of :terms_of_service
88
+ # validates_acceptance_of :eula, message: 'must be abided'
89
+ # end
90
+ #
91
+ # If the database column does not exist, the +terms_of_service+ attribute
92
+ # is entirely virtual. This check is performed only if +terms_of_service+
93
+ # is not +nil+.
94
+ #
95
+ # Configuration options:
96
+ # * <tt>:message</tt> - A custom error message (default is: "must be
97
+ # accepted").
98
+ # * <tt>:accept</tt> - Specifies a value that is considered accepted.
99
+ # Also accepts an array of possible values. The default value is
100
+ # an array ["1", true], which makes it easy to relate to an HTML
101
+ # checkbox. This should be set to, or include, +true+ if you are validating
102
+ # a database column, since the attribute is typecast from "1" to +true+
103
+ # before validation.
104
+ #
105
+ # There is also a list of default options supported by every validator:
106
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
107
+ # See ActiveModel::Validations::ClassMethods#validates for more information.
108
+ def validates_acceptance_of(*attr_names)
109
+ validates_with AcceptanceValidator, _merge_attributes(attr_names)
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,119 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Validations
5
+ # = Active \Model \Validation \Callbacks
6
+ #
7
+ # Provides an interface for any class to have ClassMethods#before_validation and
8
+ # ClassMethods#after_validation callbacks.
9
+ #
10
+ # First, include +ActiveModel::Validations::Callbacks+ from the class you are
11
+ # creating:
12
+ #
13
+ # class MyModel
14
+ # include ActiveModel::Validations::Callbacks
15
+ #
16
+ # before_validation :do_stuff_before_validation
17
+ # after_validation :do_stuff_after_validation
18
+ # end
19
+ #
20
+ # Like other <tt>before_*</tt> callbacks if +before_validation+ throws
21
+ # +:abort+ then <tt>valid?</tt> will not be called.
22
+ module Callbacks
23
+ extend ActiveSupport::Concern
24
+
25
+ included do
26
+ include ActiveSupport::Callbacks
27
+ define_callbacks :validation,
28
+ skip_after_callbacks_if_terminated: true,
29
+ scope: [:kind, :name]
30
+ end
31
+
32
+ module ClassMethods
33
+ # Defines a callback that will get called right before validation.
34
+ #
35
+ # class Person
36
+ # include ActiveModel::Validations
37
+ # include ActiveModel::Validations::Callbacks
38
+ #
39
+ # attr_accessor :name
40
+ #
41
+ # validates_length_of :name, maximum: 6
42
+ #
43
+ # before_validation :remove_whitespaces
44
+ #
45
+ # private
46
+ # def remove_whitespaces
47
+ # name.strip!
48
+ # end
49
+ # end
50
+ #
51
+ # person = Person.new
52
+ # person.name = ' bob '
53
+ # person.valid? # => true
54
+ # person.name # => "bob"
55
+ def before_validation(*args, &block)
56
+ options = args.extract_options!
57
+
58
+ set_options_for_callback(options)
59
+
60
+ set_callback(:validation, :before, *args, options, &block)
61
+ end
62
+
63
+ # Defines a callback that will get called right after validation.
64
+ #
65
+ # class Person
66
+ # include ActiveModel::Validations
67
+ # include ActiveModel::Validations::Callbacks
68
+ #
69
+ # attr_accessor :name, :status
70
+ #
71
+ # validates_presence_of :name
72
+ #
73
+ # after_validation :set_status
74
+ #
75
+ # private
76
+ # def set_status
77
+ # self.status = errors.empty?
78
+ # end
79
+ # end
80
+ #
81
+ # person = Person.new
82
+ # person.name = ''
83
+ # person.valid? # => false
84
+ # person.status # => false
85
+ # person.name = 'bob'
86
+ # person.valid? # => true
87
+ # person.status # => true
88
+ def after_validation(*args, &block)
89
+ options = args.extract_options!
90
+ options = options.dup
91
+ options[:prepend] = true
92
+
93
+ set_options_for_callback(options)
94
+
95
+ set_callback(:validation, :after, *args, options, &block)
96
+ end
97
+
98
+ private
99
+ def set_options_for_callback(options)
100
+ if options.key?(:on)
101
+ options[:on] = Array(options[:on])
102
+ options[:if] = [
103
+ ->(o) {
104
+ options[:on].intersect?(Array(o.validation_context))
105
+ },
106
+ *options[:if]
107
+ ]
108
+ end
109
+ end
110
+ end
111
+
112
+ private
113
+ # Override run_validations! to include callbacks.
114
+ def run_validations!
115
+ _run_validation_callbacks { super }
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/validations/resolve_value"
4
+ require "active_support/core_ext/range"
5
+
6
+ module ActiveModel
7
+ module Validations
8
+ module Clusivity # :nodoc:
9
+ include ResolveValue
10
+
11
+ ERROR_MESSAGE = "An object with the method #include? or a proc, lambda or symbol is required, " \
12
+ "and must be supplied as the :in (or :within) option of the configuration hash"
13
+
14
+ def check_validity!
15
+ unless delimiter.respond_to?(:include?) || delimiter.respond_to?(:call) || delimiter.respond_to?(:to_sym)
16
+ raise ArgumentError, ERROR_MESSAGE
17
+ end
18
+ end
19
+
20
+ private
21
+ def include?(record, value)
22
+ members = resolve_value(record, delimiter)
23
+
24
+ if value.is_a?(Array)
25
+ value.all? { |v| members.public_send(inclusion_method(members), v) }
26
+ else
27
+ members.public_send(inclusion_method(members), value)
28
+ end
29
+ end
30
+
31
+ def delimiter
32
+ @delimiter ||= options[:in] || options[:within]
33
+ end
34
+
35
+ # After Ruby 2.2, <tt>Range#include?</tt> on non-number-or-time-ish ranges checks all
36
+ # possible values in the range for equality, which is slower but more accurate.
37
+ # <tt>Range#cover?</tt> uses the previous logic of comparing a value with the range
38
+ # endpoints, which is fast but is only accurate on Numeric, Time, Date,
39
+ # or DateTime ranges.
40
+ def inclusion_method(enumerable)
41
+ if enumerable.is_a? Range
42
+ case enumerable.begin || enumerable.end
43
+ when Numeric, Time, DateTime, Date
44
+ :cover?
45
+ else
46
+ :include?
47
+ end
48
+ else
49
+ :include?
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,18 @@
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 error_options(value, option_value)
11
+ options.except(*COMPARE_CHECKS.keys).merge!(
12
+ count: option_value,
13
+ value: value
14
+ )
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_model/validations/comparability"
4
+ require "active_model/validations/resolve_value"
5
+
6
+ module ActiveModel
7
+ module Validations
8
+ class ComparisonValidator < EachValidator # :nodoc:
9
+ include Comparability
10
+ include ResolveValue
11
+
12
+ def check_validity!
13
+ unless options.keys.intersect?(COMPARE_CHECKS.keys)
14
+ raise ArgumentError, "Expected one of :greater_than, :greater_than_or_equal_to, "\
15
+ ":equal_to, :less_than, :less_than_or_equal_to, or :other_than option to be supplied."
16
+ end
17
+ end
18
+
19
+ def validate_each(record, attr_name, value)
20
+ options.slice(*COMPARE_CHECKS.keys).each do |option, raw_option_value|
21
+ option_value = resolve_value(record, raw_option_value)
22
+
23
+ if value.nil? || value.blank?
24
+ return record.errors.add(attr_name, :blank, **error_options(value, option_value))
25
+ end
26
+
27
+ unless value.public_send(COMPARE_CHECKS[option], option_value)
28
+ record.errors.add(attr_name, option, **error_options(value, option_value))
29
+ end
30
+ rescue ArgumentError => e
31
+ record.errors.add(attr_name, e.message)
32
+ end
33
+ end
34
+ end
35
+
36
+ module HelperMethods
37
+ # Validates the value of a specified attribute fulfills all
38
+ # defined comparisons with another value, proc, or attribute.
39
+ #
40
+ # class Person < ActiveRecord::Base
41
+ # validates_comparison_of :value, greater_than: 'the sum of its parts'
42
+ # end
43
+ #
44
+ # Configuration options:
45
+ # * <tt>:message</tt> - A custom error message (default is: "failed comparison").
46
+ # * <tt>:greater_than</tt> - Specifies the value must be greater than the
47
+ # supplied value. The default error message for this option is _"must be
48
+ # greater than %{count}"_.
49
+ # * <tt>:greater_than_or_equal_to</tt> - Specifies the value must be
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}"_.
52
+ # * <tt>:equal_to</tt> - Specifies the value must be equal to the supplied
53
+ # value. The default error message for this option is _"must be equal to
54
+ # %{count}"_.
55
+ # * <tt>:less_than</tt> - Specifies the value must be less than the
56
+ # supplied value. The default error message for this option is _"must be
57
+ # less than %{count}"_.
58
+ # * <tt>:less_than_or_equal_to</tt> - Specifies the value must be less
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}"_.
61
+ # * <tt>:other_than</tt> - Specifies the value must not be equal to the
62
+ # supplied value. The default error message for this option is _"must be
63
+ # other than %{count}"_.
64
+ #
65
+ # There is also a list of default options supported by every validator:
66
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+ .
67
+ # See ActiveModel::Validations::ClassMethods#validates for more information.
68
+ #
69
+ # The validator requires at least one of the following checks to be supplied.
70
+ # Each will accept a proc, value, or a symbol which corresponds to a method:
71
+ #
72
+ # * <tt>:greater_than</tt>
73
+ # * <tt>:greater_than_or_equal_to</tt>
74
+ # * <tt>:equal_to</tt>
75
+ # * <tt>:less_than</tt>
76
+ # * <tt>:less_than_or_equal_to</tt>
77
+ # * <tt>:other_than</tt>
78
+ #
79
+ # For example:
80
+ #
81
+ # class Person < ActiveRecord::Base
82
+ # validates_comparison_of :birth_date, less_than_or_equal_to: -> { Date.today }
83
+ # validates_comparison_of :preferred_name, other_than: :given_name, allow_nil: true
84
+ # end
85
+ def validates_comparison_of(*attr_names)
86
+ validates_with ComparisonValidator, _merge_attributes(attr_names)
87
+ end
88
+ end
89
+ end
90
+ end