Skip to content

Commit

Permalink
Add validators reflection so you can do 'Person.validators' and 'Pers…
Browse files Browse the repository at this point in the history
…on.validators_on(:name)'.

Signed-off-by: José Valim <jose.valim@gmail.com>
  • Loading branch information
sikachu authored and josevalim committed Feb 21, 2010
1 parent 250c809 commit 8f97e9d
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 3 deletions.
21 changes: 18 additions & 3 deletions activemodel/lib/active_model/validations.rb
@@ -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'

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
9 changes: 9 additions & 0 deletions activemodel/lib/active_model/validations/with.rb
Expand Up @@ -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
Expand Down
18 changes: 18 additions & 0 deletions activemodel/lib/active_model/validator.rb
@@ -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+
Expand Down Expand Up @@ -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)
Expand Down
1 change: 1 addition & 0 deletions activemodel/test/cases/validations/with_validation_test.rb
Expand Up @@ -9,6 +9,7 @@ class ValidatesWithTest < ActiveRecord::TestCase

def teardown
Topic.reset_callbacks(:validate)
Topic._validators.clear
end

ERROR_MESSAGE = "Validation error from validator"
Expand Down
27 changes: 27 additions & 0 deletions activemodel/test/cases/validations_test.rb
Expand Up @@ -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
Expand Down Expand Up @@ -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

@adrianpacala
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@bumi
Copy link

@bumi bumi commented on 8f97e9d Feb 21, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

like +1 ;)

@ryanb
Copy link
Contributor

@ryanb ryanb commented on 8f97e9d Feb 21, 2010

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

@danielmorrison
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This made my week. Fantastic!

@grzegorzkazulak
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome.

@anildigital
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

Please sign in to comment.