Skip to content
This repository
Browse code

Add validators reflection so you can do 'Person.validators' and 'Pers…

…on.validators_on(:name)'.

Signed-off-by: José Valim <jose.valim@gmail.com>
  • Loading branch information...
commit 8f97e9d19abf02b33c5f7c0c1f1d5daf13e28893 1 parent 250c809
Prem Sichanugrist sikachu authored josevalim committed
21 activemodel/lib/active_model/validations.rb
... ... @@ -1,4 +1,5 @@
1 1 require 'active_support/core_ext/array/extract_options'
  2 +require 'active_support/core_ext/class/attribute'
2 3 require 'active_support/core_ext/hash/keys'
3 4 require 'active_model/errors'
4 5
@@ -45,6 +46,9 @@ module Validations
45 46 included do
46 47 extend ActiveModel::Translation
47 48 define_callbacks :validate, :scope => :name
  49 +
  50 + class_attribute :_validators
  51 + self._validators = Hash.new { |h,k| h[k] = [] }
48 52 end
49 53
50 54 module ClassMethods
@@ -117,9 +121,20 @@ def validate(*args, &block)
117 121 end
118 122 set_callback(:validate, *args, &block)
119 123 end
120   -
121   - private
122   -
  124 +
  125 + # List all validators that being used to validate the model using +validates_with+
  126 + # method.
  127 + def validators
  128 + _validators.values.flatten.uniq
  129 + end
  130 +
  131 + # List all validators that being used to validate a specific attribute.
  132 + def validators_on(attribute)
  133 + _validators[attribute.to_sym]
  134 + end
  135 +
  136 + private
  137 +
123 138 def _merge_attributes(attr_names)
124 139 options = attr_names.extract_options!
125 140 options.merge(:attributes => attr_names)
9 activemodel/lib/active_model/validations/with.rb
@@ -62,6 +62,15 @@ def validates_with(*args, &block)
62 62 args.each do |klass|
63 63 validator = klass.new(options, &block)
64 64 validator.setup(self) if validator.respond_to?(:setup)
  65 +
  66 + if validator.respond_to?(:attributes) && !validator.attributes.empty?
  67 + validator.attributes.each do |attribute|
  68 + _validators[attribute.to_sym] << validator
  69 + end
  70 + else
  71 + _validators[nil] << validator
  72 + end
  73 +
65 74 validate(validator, options)
66 75 end
67 76 end
18 activemodel/lib/active_model/validator.rb
... ... @@ -1,3 +1,5 @@
  1 +require "active_support/core_ext/module/anonymous"
  2 +
1 3 module ActiveModel #:nodoc:
2 4 # A simple base class that can be used along with
3 5 # +ActiveModel::Validations::ClassMethods.validates_with+
@@ -88,11 +90,27 @@ module ActiveModel #:nodoc:
88 90 class Validator
89 91 attr_reader :options
90 92
  93 + # Returns the kind of the validator.
  94 + #
  95 + # == Examples
  96 + #
  97 + # PresenceValidator.kind #=> :presence
  98 + # UniquenessValidator.kind #=> :uniqueness
  99 + #
  100 + def self.kind
  101 + @kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous?
  102 + end
  103 +
91 104 # Accepts options that will be made availible through the +options+ reader.
92 105 def initialize(options)
93 106 @options = options
94 107 end
95 108
  109 + # Return the kind for this validator.
  110 + def kind
  111 + self.class.kind
  112 + end
  113 +
96 114 # Override this method in subclasses with validation logic, adding errors
97 115 # to the records +errors+ array where necessary.
98 116 def validate(record)
1  activemodel/test/cases/validations/with_validation_test.rb
@@ -9,6 +9,7 @@ class ValidatesWithTest < ActiveRecord::TestCase
9 9
10 10 def teardown
11 11 Topic.reset_callbacks(:validate)
  12 + Topic._validators.clear
12 13 end
13 14
14 15 ERROR_MESSAGE = "Validation error from validator"
27 activemodel/test/cases/validations_test.rb
@@ -10,6 +10,10 @@
10 10 class ValidationsTest < ActiveModel::TestCase
11 11 include ActiveModel::TestsDatabase
12 12
  13 + def setup
  14 + Topic._validators.clear
  15 + end
  16 +
13 17 # Most of the tests mess with the validations of Topic, so lets repair it all the time.
14 18 # Other classes we mess with will be dealt with in the specific tests
15 19 def teardown
@@ -220,4 +224,27 @@ def test_validation_with_message_as_proc
220 224 assert !t.valid?
221 225 assert ["NO BLANKS HERE"], t.errors[:title]
222 226 end
  227 +
  228 + def test_list_of_validators_for_model
  229 + Topic.validates_presence_of :title
  230 + Topic.validates_length_of :title, :minimum => 2
  231 +
  232 + assert_equal 2, Topic.validators.count
  233 + assert_equal [:presence, :length], Topic.validators.map(&:kind)
  234 + end
  235 +
  236 + def test_list_of_validators_on_an_attribute
  237 + Topic.validates_presence_of :title, :content
  238 + Topic.validates_length_of :title, :minimum => 2
  239 +
  240 + assert_equal 2, Topic.validators_on(:title).count
  241 + assert_equal [:presence, :length], Topic.validators_on(:title).map(&:kind)
  242 + assert_equal 1, Topic.validators_on(:content).count
  243 + assert_equal [:presence], Topic.validators_on(:content).map(&:kind)
  244 + end
  245 +
  246 + def test_accessing_instance_of_validator_on_an_attribute
  247 + Topic.validates_length_of :title, :minimum => 10
  248 + assert_equal 10, Topic.validators_on(:title).first.options[:minimum]
  249 + end
223 250 end

6 comments on commit 8f97e9d

Adrian Pacała

I demand "like this commit" feature on GitHub so I can like this commit.

Michael Bumann

like +1 ;)

Ryan Bates

Thank you! It's about time this got in. :)

Daniel Morrison

This made my week. Fantastic!

Grzegorz Kazulak

Awesome.

Anil Wadghule

Nice!

Please sign in to comment.
Something went wrong with that request. Please try again.