omg-activemodel 8.0.0.alpha1

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 (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