activemodel 5.2.3

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 (67) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +114 -0
  3. data/MIT-LICENSE +21 -0
  4. data/README.rdoc +264 -0
  5. data/lib/active_model.rb +77 -0
  6. data/lib/active_model/attribute.rb +248 -0
  7. data/lib/active_model/attribute/user_provided_default.rb +52 -0
  8. data/lib/active_model/attribute_assignment.rb +57 -0
  9. data/lib/active_model/attribute_methods.rb +478 -0
  10. data/lib/active_model/attribute_mutation_tracker.rb +124 -0
  11. data/lib/active_model/attribute_set.rb +114 -0
  12. data/lib/active_model/attribute_set/builder.rb +126 -0
  13. data/lib/active_model/attribute_set/yaml_encoder.rb +41 -0
  14. data/lib/active_model/attributes.rb +111 -0
  15. data/lib/active_model/callbacks.rb +153 -0
  16. data/lib/active_model/conversion.rb +111 -0
  17. data/lib/active_model/dirty.rb +343 -0
  18. data/lib/active_model/errors.rb +517 -0
  19. data/lib/active_model/forbidden_attributes_protection.rb +31 -0
  20. data/lib/active_model/gem_version.rb +17 -0
  21. data/lib/active_model/lint.rb +118 -0
  22. data/lib/active_model/locale/en.yml +36 -0
  23. data/lib/active_model/model.rb +99 -0
  24. data/lib/active_model/naming.rb +318 -0
  25. data/lib/active_model/railtie.rb +14 -0
  26. data/lib/active_model/secure_password.rb +129 -0
  27. data/lib/active_model/serialization.rb +192 -0
  28. data/lib/active_model/serializers/json.rb +146 -0
  29. data/lib/active_model/translation.rb +70 -0
  30. data/lib/active_model/type.rb +53 -0
  31. data/lib/active_model/type/big_integer.rb +15 -0
  32. data/lib/active_model/type/binary.rb +52 -0
  33. data/lib/active_model/type/boolean.rb +38 -0
  34. data/lib/active_model/type/date.rb +57 -0
  35. data/lib/active_model/type/date_time.rb +51 -0
  36. data/lib/active_model/type/decimal.rb +70 -0
  37. data/lib/active_model/type/float.rb +36 -0
  38. data/lib/active_model/type/helpers.rb +7 -0
  39. data/lib/active_model/type/helpers/accepts_multiparameter_time.rb +41 -0
  40. data/lib/active_model/type/helpers/mutable.rb +20 -0
  41. data/lib/active_model/type/helpers/numeric.rb +37 -0
  42. data/lib/active_model/type/helpers/time_value.rb +68 -0
  43. data/lib/active_model/type/helpers/timezone.rb +19 -0
  44. data/lib/active_model/type/immutable_string.rb +32 -0
  45. data/lib/active_model/type/integer.rb +70 -0
  46. data/lib/active_model/type/registry.rb +70 -0
  47. data/lib/active_model/type/string.rb +26 -0
  48. data/lib/active_model/type/time.rb +51 -0
  49. data/lib/active_model/type/value.rb +126 -0
  50. data/lib/active_model/validations.rb +439 -0
  51. data/lib/active_model/validations/absence.rb +33 -0
  52. data/lib/active_model/validations/acceptance.rb +106 -0
  53. data/lib/active_model/validations/callbacks.rb +122 -0
  54. data/lib/active_model/validations/clusivity.rb +54 -0
  55. data/lib/active_model/validations/confirmation.rb +80 -0
  56. data/lib/active_model/validations/exclusion.rb +49 -0
  57. data/lib/active_model/validations/format.rb +114 -0
  58. data/lib/active_model/validations/helper_methods.rb +15 -0
  59. data/lib/active_model/validations/inclusion.rb +47 -0
  60. data/lib/active_model/validations/length.rb +129 -0
  61. data/lib/active_model/validations/numericality.rb +189 -0
  62. data/lib/active_model/validations/presence.rb +39 -0
  63. data/lib/active_model/validations/validates.rb +174 -0
  64. data/lib/active_model/validations/with.rb +147 -0
  65. data/lib/active_model/validator.rb +183 -0
  66. data/lib/active_model/version.rb +10 -0
  67. metadata +125 -0
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveModel
4
+ module Validations
5
+ class PresenceValidator < EachValidator # :nodoc:
6
+ def validate_each(record, attr_name, value)
7
+ record.errors.add(attr_name, :blank, options) if value.blank?
8
+ end
9
+ end
10
+
11
+ module HelperMethods
12
+ # Validates that the specified attributes are not blank (as defined by
13
+ # Object#blank?). Happens by default on save.
14
+ #
15
+ # class Person < ActiveRecord::Base
16
+ # validates_presence_of :first_name
17
+ # end
18
+ #
19
+ # The first_name attribute must be in the object and it cannot be blank.
20
+ #
21
+ # If you want to validate the presence of a boolean field (where the real
22
+ # values are +true+ and +false+), you will want to use
23
+ # <tt>validates_inclusion_of :field_name, in: [true, false]</tt>.
24
+ #
25
+ # This is due to the way Object#blank? handles boolean values:
26
+ # <tt>false.blank? # => true</tt>.
27
+ #
28
+ # Configuration options:
29
+ # * <tt>:message</tt> - A custom error message (default is: "can't be blank").
30
+ #
31
+ # There is also a list of default options supported by every validator:
32
+ # +:if+, +:unless+, +:on+, +:allow_nil+, +:allow_blank+, and +:strict+.
33
+ # See <tt>ActiveModel::Validations#validates</tt> for more information
34
+ def validates_presence_of(*attr_names)
35
+ validates_with PresenceValidator, _merge_attributes(attr_names)
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,174 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/hash/slice"
4
+
5
+ module ActiveModel
6
+ module Validations
7
+ module ClassMethods
8
+ # This method is a shortcut to all default validators and any custom
9
+ # validator classes ending in 'Validator'. Note that Rails default
10
+ # validators can be overridden inside specific classes by creating
11
+ # custom validator classes in their place such as PresenceValidator.
12
+ #
13
+ # Examples of using the default rails validators:
14
+ #
15
+ # validates :terms, acceptance: true
16
+ # validates :password, confirmation: true
17
+ # validates :username, exclusion: { in: %w(admin superuser) }
18
+ # validates :email, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i, on: :create }
19
+ # validates :age, inclusion: { in: 0..9 }
20
+ # validates :first_name, length: { maximum: 30 }
21
+ # validates :age, numericality: true
22
+ # validates :username, presence: true
23
+ #
24
+ # The power of the +validates+ method comes when using custom validators
25
+ # and default validators in one call for a given attribute.
26
+ #
27
+ # class EmailValidator < ActiveModel::EachValidator
28
+ # def validate_each(record, attribute, value)
29
+ # record.errors.add attribute, (options[:message] || "is not an email") unless
30
+ # value =~ /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
31
+ # end
32
+ # end
33
+ #
34
+ # class Person
35
+ # include ActiveModel::Validations
36
+ # attr_accessor :name, :email
37
+ #
38
+ # validates :name, presence: true, length: { maximum: 100 }
39
+ # validates :email, presence: true, email: true
40
+ # end
41
+ #
42
+ # Validator classes may also exist within the class being validated
43
+ # allowing custom modules of validators to be included as needed.
44
+ #
45
+ # class Film
46
+ # include ActiveModel::Validations
47
+ #
48
+ # class TitleValidator < ActiveModel::EachValidator
49
+ # def validate_each(record, attribute, value)
50
+ # record.errors.add attribute, "must start with 'the'" unless value =~ /\Athe/i
51
+ # end
52
+ # end
53
+ #
54
+ # validates :name, title: true
55
+ # end
56
+ #
57
+ # Additionally validator classes may be in another namespace and still
58
+ # used within any class.
59
+ #
60
+ # validates :name, :'film/title' => true
61
+ #
62
+ # The validators hash can also handle regular expressions, ranges, arrays
63
+ # and strings in shortcut form.
64
+ #
65
+ # validates :email, format: /@/
66
+ # validates :gender, inclusion: %w(male female)
67
+ # validates :password, length: 6..20
68
+ #
69
+ # When using shortcut form, ranges and arrays are passed to your
70
+ # validator's initializer as <tt>options[:in]</tt> while other types
71
+ # including regular expressions and strings are passed as <tt>options[:with]</tt>.
72
+ #
73
+ # There is also a list of options that could be used along with validators:
74
+ #
75
+ # * <tt>:on</tt> - Specifies the contexts where this validation is active.
76
+ # Runs in all validation contexts by default +nil+. You can pass a symbol
77
+ # or an array of symbols. (e.g. <tt>on: :create</tt> or
78
+ # <tt>on: :custom_validation_context</tt> or
79
+ # <tt>on: [:create, :custom_validation_context]</tt>)
80
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
81
+ # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
82
+ # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>). The method,
83
+ # proc or string should return or evaluate to a +true+ or +false+ value.
84
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to determine
85
+ # if the validation should not occur (e.g. <tt>unless: :skip_validation</tt>,
86
+ # or <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>). The
87
+ # method, proc or string should return or evaluate to a +true+ or
88
+ # +false+ value.
89
+ # * <tt>:allow_nil</tt> - Skip validation if the attribute is +nil+.
90
+ # * <tt>:allow_blank</tt> - Skip validation if the attribute is blank.
91
+ # * <tt>:strict</tt> - If the <tt>:strict</tt> option is set to true
92
+ # will raise ActiveModel::StrictValidationFailed instead of adding the error.
93
+ # <tt>:strict</tt> option can also be set to any other exception.
94
+ #
95
+ # Example:
96
+ #
97
+ # validates :password, presence: true, confirmation: true, if: :password_required?
98
+ # validates :token, length: 24, strict: TokenLengthException
99
+ #
100
+ #
101
+ # Finally, the options +:if+, +:unless+, +:on+, +:allow_blank+, +:allow_nil+, +:strict+
102
+ # and +:message+ can be given to one specific validator, as a hash:
103
+ #
104
+ # validates :password, presence: { if: :password_required?, message: 'is forgotten.' }, confirmation: true
105
+ def validates(*attributes)
106
+ defaults = attributes.extract_options!.dup
107
+ validations = defaults.slice!(*_validates_default_keys)
108
+
109
+ raise ArgumentError, "You need to supply at least one attribute" if attributes.empty?
110
+ raise ArgumentError, "You need to supply at least one validation" if validations.empty?
111
+
112
+ defaults[:attributes] = attributes
113
+
114
+ validations.each do |key, options|
115
+ next unless options
116
+ key = "#{key.to_s.camelize}Validator"
117
+
118
+ begin
119
+ validator = key.include?("::".freeze) ? key.constantize : const_get(key)
120
+ rescue NameError
121
+ raise ArgumentError, "Unknown validator: '#{key}'"
122
+ end
123
+
124
+ validates_with(validator, defaults.merge(_parse_validates_options(options)))
125
+ end
126
+ end
127
+
128
+ # This method is used to define validations that cannot be corrected by end
129
+ # users and are considered exceptional. So each validator defined with bang
130
+ # or <tt>:strict</tt> option set to <tt>true</tt> will always raise
131
+ # <tt>ActiveModel::StrictValidationFailed</tt> instead of adding error
132
+ # when validation fails. See <tt>validates</tt> for more information about
133
+ # the validation itself.
134
+ #
135
+ # class Person
136
+ # include ActiveModel::Validations
137
+ #
138
+ # attr_accessor :name
139
+ # validates! :name, presence: true
140
+ # end
141
+ #
142
+ # person = Person.new
143
+ # person.name = ''
144
+ # person.valid?
145
+ # # => ActiveModel::StrictValidationFailed: Name can't be blank
146
+ def validates!(*attributes)
147
+ options = attributes.extract_options!
148
+ options[:strict] = true
149
+ validates(*(attributes << options))
150
+ end
151
+
152
+ private
153
+
154
+ # When creating custom validators, it might be useful to be able to specify
155
+ # additional default keys. This can be done by overwriting this method.
156
+ def _validates_default_keys
157
+ [:if, :unless, :on, :allow_blank, :allow_nil, :strict]
158
+ end
159
+
160
+ def _parse_validates_options(options)
161
+ case options
162
+ when TrueClass
163
+ {}
164
+ when Hash
165
+ options
166
+ when Range, Array
167
+ { in: options }
168
+ else
169
+ { with: options }
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/array/extract_options"
4
+
5
+ module ActiveModel
6
+ module Validations
7
+ class WithValidator < EachValidator # :nodoc:
8
+ def validate_each(record, attr, val)
9
+ method_name = options[:with]
10
+
11
+ if record.method(method_name).arity == 0
12
+ record.send method_name
13
+ else
14
+ record.send method_name, attr
15
+ end
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ # Passes the record off to the class or classes specified and allows them
21
+ # to add errors based on more complex conditions.
22
+ #
23
+ # class Person
24
+ # include ActiveModel::Validations
25
+ # validates_with MyValidator
26
+ # end
27
+ #
28
+ # class MyValidator < ActiveModel::Validator
29
+ # def validate(record)
30
+ # if some_complex_logic
31
+ # record.errors.add :base, 'This record is invalid'
32
+ # end
33
+ # end
34
+ #
35
+ # private
36
+ # def some_complex_logic
37
+ # # ...
38
+ # end
39
+ # end
40
+ #
41
+ # You may also pass it multiple classes, like so:
42
+ #
43
+ # class Person
44
+ # include ActiveModel::Validations
45
+ # validates_with MyValidator, MyOtherValidator, on: :create
46
+ # end
47
+ #
48
+ # Configuration options:
49
+ # * <tt>:on</tt> - Specifies the contexts where this validation is active.
50
+ # Runs in all validation contexts by default +nil+. You can pass a symbol
51
+ # or an array of symbols. (e.g. <tt>on: :create</tt> or
52
+ # <tt>on: :custom_validation_context</tt> or
53
+ # <tt>on: [:create, :custom_validation_context]</tt>)
54
+ # * <tt>:if</tt> - Specifies a method, proc or string to call to determine
55
+ # if the validation should occur (e.g. <tt>if: :allow_validation</tt>,
56
+ # or <tt>if: Proc.new { |user| user.signup_step > 2 }</tt>).
57
+ # The method, proc or string should return or evaluate to a +true+ or
58
+ # +false+ value.
59
+ # * <tt>:unless</tt> - Specifies a method, proc or string to call to
60
+ # determine if the validation should not occur
61
+ # (e.g. <tt>unless: :skip_validation</tt>, or
62
+ # <tt>unless: Proc.new { |user| user.signup_step <= 2 }</tt>).
63
+ # The method, proc or string should return or evaluate to a +true+ or
64
+ # +false+ value.
65
+ # * <tt>:strict</tt> - Specifies whether validation should be strict.
66
+ # See <tt>ActiveModel::Validations#validates!</tt> for more information.
67
+ #
68
+ # If you pass any additional configuration options, they will be passed
69
+ # to the class and available as +options+:
70
+ #
71
+ # class Person
72
+ # include ActiveModel::Validations
73
+ # validates_with MyValidator, my_custom_key: 'my custom value'
74
+ # end
75
+ #
76
+ # class MyValidator < ActiveModel::Validator
77
+ # def validate(record)
78
+ # options[:my_custom_key] # => "my custom value"
79
+ # end
80
+ # end
81
+ def validates_with(*args, &block)
82
+ options = args.extract_options!
83
+ options[:class] = self
84
+
85
+ args.each do |klass|
86
+ validator = klass.new(options, &block)
87
+
88
+ if validator.respond_to?(:attributes) && !validator.attributes.empty?
89
+ validator.attributes.each do |attribute|
90
+ _validators[attribute.to_sym] << validator
91
+ end
92
+ else
93
+ _validators[nil] << validator
94
+ end
95
+
96
+ validate(validator, options)
97
+ end
98
+ end
99
+ end
100
+
101
+ # Passes the record off to the class or classes specified and allows them
102
+ # to add errors based on more complex conditions.
103
+ #
104
+ # class Person
105
+ # include ActiveModel::Validations
106
+ #
107
+ # validate :instance_validations
108
+ #
109
+ # def instance_validations
110
+ # validates_with MyValidator
111
+ # end
112
+ # end
113
+ #
114
+ # Please consult the class method documentation for more information on
115
+ # creating your own validator.
116
+ #
117
+ # You may also pass it multiple classes, like so:
118
+ #
119
+ # class Person
120
+ # include ActiveModel::Validations
121
+ #
122
+ # validate :instance_validations, on: :create
123
+ #
124
+ # def instance_validations
125
+ # validates_with MyValidator, MyOtherValidator
126
+ # end
127
+ # end
128
+ #
129
+ # Standard configuration options (<tt>:on</tt>, <tt>:if</tt> and
130
+ # <tt>:unless</tt>), which are available on the class version of
131
+ # +validates_with+, should instead be placed on the +validates+ method
132
+ # as these are applied and tested in the callback.
133
+ #
134
+ # If you pass any additional configuration options, they will be passed
135
+ # to the class and available as +options+, please refer to the
136
+ # class version of this method for more information.
137
+ def validates_with(*args, &block)
138
+ options = args.extract_options!
139
+ options[:class] = self.class
140
+
141
+ args.each do |klass|
142
+ validator = klass.new(options, &block)
143
+ validator.validate(self)
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,183 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "active_support/core_ext/module/anonymous"
4
+
5
+ module ActiveModel
6
+ # == Active \Model \Validator
7
+ #
8
+ # A simple base class that can be used along with
9
+ # ActiveModel::Validations::ClassMethods.validates_with
10
+ #
11
+ # class Person
12
+ # include ActiveModel::Validations
13
+ # validates_with MyValidator
14
+ # end
15
+ #
16
+ # class MyValidator < ActiveModel::Validator
17
+ # def validate(record)
18
+ # if some_complex_logic
19
+ # record.errors.add(:base, "This record is invalid")
20
+ # end
21
+ # end
22
+ #
23
+ # private
24
+ # def some_complex_logic
25
+ # # ...
26
+ # end
27
+ # end
28
+ #
29
+ # Any class that inherits from ActiveModel::Validator must implement a method
30
+ # called +validate+ which accepts a +record+.
31
+ #
32
+ # class Person
33
+ # include ActiveModel::Validations
34
+ # validates_with MyValidator
35
+ # end
36
+ #
37
+ # class MyValidator < ActiveModel::Validator
38
+ # def validate(record)
39
+ # record # => The person instance being validated
40
+ # options # => Any non-standard options passed to validates_with
41
+ # end
42
+ # end
43
+ #
44
+ # To cause a validation error, you must add to the +record+'s errors directly
45
+ # from within the validators message.
46
+ #
47
+ # class MyValidator < ActiveModel::Validator
48
+ # def validate(record)
49
+ # record.errors.add :base, "This is some custom error message"
50
+ # record.errors.add :first_name, "This is some complex validation"
51
+ # # etc...
52
+ # end
53
+ # end
54
+ #
55
+ # To add behavior to the initialize method, use the following signature:
56
+ #
57
+ # class MyValidator < ActiveModel::Validator
58
+ # def initialize(options)
59
+ # super
60
+ # @my_custom_field = options[:field_name] || :first_name
61
+ # end
62
+ # end
63
+ #
64
+ # Note that the validator is initialized only once for the whole application
65
+ # life cycle, and not on each validation run.
66
+ #
67
+ # The easiest way to add custom validators for validating individual attributes
68
+ # is with the convenient <tt>ActiveModel::EachValidator</tt>.
69
+ #
70
+ # class TitleValidator < ActiveModel::EachValidator
71
+ # def validate_each(record, attribute, value)
72
+ # record.errors.add attribute, 'must be Mr., Mrs., or Dr.' unless %w(Mr. Mrs. Dr.).include?(value)
73
+ # end
74
+ # end
75
+ #
76
+ # This can now be used in combination with the +validates+ method
77
+ # (see <tt>ActiveModel::Validations::ClassMethods.validates</tt> for more on this).
78
+ #
79
+ # class Person
80
+ # include ActiveModel::Validations
81
+ # attr_accessor :title
82
+ #
83
+ # validates :title, presence: true, title: true
84
+ # end
85
+ #
86
+ # It can be useful to access the class that is using that validator when there are prerequisites such
87
+ # as an +attr_accessor+ being present. This class is accessible via <tt>options[:class]</tt> in the constructor.
88
+ # To setup your validator override the constructor.
89
+ #
90
+ # class MyValidator < ActiveModel::Validator
91
+ # def initialize(options={})
92
+ # super
93
+ # options[:class].send :attr_accessor, :custom_attribute
94
+ # end
95
+ # end
96
+ class Validator
97
+ attr_reader :options
98
+
99
+ # Returns the kind of the validator.
100
+ #
101
+ # PresenceValidator.kind # => :presence
102
+ # AcceptanceValidator.kind # => :acceptance
103
+ def self.kind
104
+ @kind ||= name.split("::").last.underscore.chomp("_validator").to_sym unless anonymous?
105
+ end
106
+
107
+ # Accepts options that will be made available through the +options+ reader.
108
+ def initialize(options = {})
109
+ @options = options.except(:class).freeze
110
+ end
111
+
112
+ # Returns the kind for this validator.
113
+ #
114
+ # PresenceValidator.new(attributes: [:username]).kind # => :presence
115
+ # AcceptanceValidator.new(attributes: [:terms]).kind # => :acceptance
116
+ def kind
117
+ self.class.kind
118
+ end
119
+
120
+ # Override this method in subclasses with validation logic, adding errors
121
+ # to the records +errors+ array where necessary.
122
+ def validate(record)
123
+ raise NotImplementedError, "Subclasses must implement a validate(record) method."
124
+ end
125
+ end
126
+
127
+ # +EachValidator+ is a validator which iterates through the attributes given
128
+ # in the options hash invoking the <tt>validate_each</tt> method passing in the
129
+ # record, attribute and value.
130
+ #
131
+ # All \Active \Model validations are built on top of this validator.
132
+ class EachValidator < Validator #:nodoc:
133
+ attr_reader :attributes
134
+
135
+ # Returns a new validator instance. All options will be available via the
136
+ # +options+ reader, however the <tt>:attributes</tt> option will be removed
137
+ # and instead be made available through the +attributes+ reader.
138
+ def initialize(options)
139
+ @attributes = Array(options.delete(:attributes))
140
+ raise ArgumentError, ":attributes cannot be blank" if @attributes.empty?
141
+ super
142
+ check_validity!
143
+ end
144
+
145
+ # Performs validation on the supplied record. By default this will call
146
+ # +validate_each+ to determine validity therefore subclasses should
147
+ # override +validate_each+ with validation logic.
148
+ def validate(record)
149
+ attributes.each do |attribute|
150
+ value = record.read_attribute_for_validation(attribute)
151
+ next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank])
152
+ validate_each(record, attribute, value)
153
+ end
154
+ end
155
+
156
+ # Override this method in subclasses with the validation logic, adding
157
+ # errors to the records +errors+ array where necessary.
158
+ def validate_each(record, attribute, value)
159
+ raise NotImplementedError, "Subclasses must implement a validate_each(record, attribute, value) method"
160
+ end
161
+
162
+ # Hook method that gets called by the initializer allowing verification
163
+ # that the arguments supplied are valid. You could for example raise an
164
+ # +ArgumentError+ when invalid options are supplied.
165
+ def check_validity!
166
+ end
167
+ end
168
+
169
+ # +BlockValidator+ is a special +EachValidator+ which receives a block on initialization
170
+ # and call this block for each attribute being validated. +validates_each+ uses this validator.
171
+ class BlockValidator < EachValidator #:nodoc:
172
+ def initialize(options, &block)
173
+ @block = block
174
+ super
175
+ end
176
+
177
+ private
178
+
179
+ def validate_each(record, attribute, value)
180
+ @block.call(record, attribute, value)
181
+ end
182
+ end
183
+ end