Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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
View
@@ -1,4 +1,5 @@
require 'active_support/core_ext/array/extract_options'
+require 'active_support/core_ext/class/attribute'
require 'active_support/core_ext/hash/keys'
require 'active_model/errors'
@@ -45,6 +46,9 @@ module Validations
included do
extend ActiveModel::Translation
define_callbacks :validate, :scope => :name
+
+ class_attribute :_validators
+ self._validators = Hash.new { |h,k| h[k] = [] }
end
module ClassMethods
@@ -117,9 +121,20 @@ def validate(*args, &block)
end
set_callback(:validate, *args, &block)
end
-
- private
-
+
+ # List all validators that being used to validate the model using +validates_with+
+ # method.
+ def validators
+ _validators.values.flatten.uniq
+ end
+
+ # List all validators that being used to validate a specific attribute.
+ def validators_on(attribute)
+ _validators[attribute.to_sym]
+ end
+
+ private
+
def _merge_attributes(attr_names)
options = attr_names.extract_options!
options.merge(:attributes => attr_names)
9 activemodel/lib/active_model/validations/with.rb
View
@@ -62,6 +62,15 @@ def validates_with(*args, &block)
args.each do |klass|
validator = klass.new(options, &block)
validator.setup(self) if validator.respond_to?(:setup)
+
+ if validator.respond_to?(:attributes) && !validator.attributes.empty?
+ validator.attributes.each do |attribute|
+ _validators[attribute.to_sym] << validator
+ end
+ else
+ _validators[nil] << validator
+ end
+
validate(validator, options)
end
end
18 activemodel/lib/active_model/validator.rb
View
@@ -1,3 +1,5 @@
+require "active_support/core_ext/module/anonymous"
+
module ActiveModel #:nodoc:
# A simple base class that can be used along with
# +ActiveModel::Validations::ClassMethods.validates_with+
@@ -88,11 +90,27 @@ module ActiveModel #:nodoc:
class Validator
attr_reader :options
+ # Returns the kind of the validator.
+ #
+ # == Examples
+ #
+ # PresenceValidator.kind #=> :presence
+ # UniquenessValidator.kind #=> :uniqueness
+ #
+ def self.kind
+ @kind ||= name.split('::').last.underscore.sub(/_validator$/, '').to_sym unless anonymous?
+ end
+
# Accepts options that will be made availible through the +options+ reader.
def initialize(options)
@options = options
end
+ # Return the kind for this validator.
+ def kind
+ self.class.kind
+ end
+
# Override this method in subclasses with validation logic, adding errors
# to the records +errors+ array where necessary.
def validate(record)
1  activemodel/test/cases/validations/with_validation_test.rb
View
@@ -9,6 +9,7 @@ class ValidatesWithTest < ActiveRecord::TestCase
def teardown
Topic.reset_callbacks(:validate)
+ Topic._validators.clear
end
ERROR_MESSAGE = "Validation error from validator"
27 activemodel/test/cases/validations_test.rb
View
@@ -10,6 +10,10 @@
class ValidationsTest < ActiveModel::TestCase
include ActiveModel::TestsDatabase
+ def setup
+ Topic._validators.clear
+ end
+
# Most of the tests mess with the validations of Topic, so lets repair it all the time.
# Other classes we mess with will be dealt with in the specific tests
def teardown
@@ -220,4 +224,27 @@ def test_validation_with_message_as_proc
assert !t.valid?
assert ["NO BLANKS HERE"], t.errors[:title]
end
+
+ def test_list_of_validators_for_model
+ Topic.validates_presence_of :title
+ Topic.validates_length_of :title, :minimum => 2
+
+ assert_equal 2, Topic.validators.count
+ assert_equal [:presence, :length], Topic.validators.map(&:kind)
+ end
+
+ def test_list_of_validators_on_an_attribute
+ Topic.validates_presence_of :title, :content
+ Topic.validates_length_of :title, :minimum => 2
+
+ assert_equal 2, Topic.validators_on(:title).count
+ assert_equal [:presence, :length], Topic.validators_on(:title).map(&:kind)
+ assert_equal 1, Topic.validators_on(:content).count
+ assert_equal [:presence], Topic.validators_on(:content).map(&:kind)
+ end
+
+ def test_accessing_instance_of_validator_on_an_attribute
+ Topic.validates_length_of :title, :minimum => 10
+ assert_equal 10, Topic.validators_on(:title).first.options[:minimum]
+ end
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.