activemodel 5.2.3

Sign up to get free protection for your applications and to get access to all the features.
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