Created
March 27, 2010 07:08
-
-
Save mcmire/345789 to your computer and use it in GitHub Desktop.
Run a single validation on a single attribute
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module ActiveRecord | |
class Base | |
VALIDATION_SYMBOLS = %w( | |
acceptance confirmation exclusion format inclusion length numericality presence size uniqueness | |
).inject({}) {|hash, name| hash["validates_#{name}_of"] = name; hash } | |
VALIDATION_METHODS = VALIDATION_SYMBOLS.invert | |
class << self | |
def validations | |
@validations ||= {} | |
end | |
# Override validates_each so that each validation routine is stored in a hash keyed by the name | |
# of the validation method and the name of the attribute being validated | |
# Every validation method uses this except validates_presence_of | |
def validates_each(*attrs, &block) | |
options = attrs.extract_options!.symbolize_keys | |
attrs = attrs.flatten | |
# Declare the validation. | |
validation_routine = lambda do |record, attr| | |
value = record.send(attr) | |
next if (value.nil? && options[:allow_nil]) || (value.blank? && options[:allow_blank]) | |
block.call(record, attr, value) | |
end | |
attrs.each {|attr| (validations[attr.to_s] ||= {})[options[:name].to_s] = validation_routine } | |
send(validation_method(options[:on] || :save), options) do |record| | |
attrs.each {|attr| validation_routine.call(record, attr) } | |
end | |
end | |
# Override validates_presence_of (which is the only method that doesn't use validates_each) so that each | |
# validation routine is stored in a hash keyed by the name of the validation method and the name of the | |
# attribute being validated | |
def validates_presence_of(*attrs) | |
configuration = { :message => ActiveRecord::Errors.default_error_messages[:blank], :on => :save, :name => "validates_presence_of" } | |
configuration.update(attrs.extract_options!) | |
validation_routine = lambda do |record, attr| | |
record.errors.add_on_blank(attr, configuration[:message]) | |
end | |
attrs.each {|attr| (validations[attr.to_s] ||= {})[configuration[:name].to_s] = validation_routine } | |
# can't use validates_each here, because it cannot cope with nonexistent attributes, | |
# while errors.add_on_empty can | |
send(validation_method(configuration[:on]), configuration) do |record| | |
attrs.each {|attr| validation_routine.call(record, attr) } | |
end | |
end | |
# Intercept the other validation methods so that the name of the method is passed as a configuration option | |
# so we can use it when storing the validation routine in the 'validations' hash | |
(VALIDATION_SYMBOLS.keys - ['validates_presence_of']).each do |method| | |
class_eval <<-EOC, __FILE__, __LINE__ | |
def #{method}_with_single_attr_validations(*attrs) | |
options = attrs.extract_options!.symbolize_keys | |
options[:name] = "#{method}" | |
#{method}_without_single_attr_validations(*(attrs << options)) | |
end | |
alias_method_chain :#{method}, :single_attr_validations | |
EOC | |
end | |
# Intercept custom validation callbacks so that block is stored in 'validations' hash as well | |
# If a given method is a two-element array (e.g. [:permaname, :must_be_unique]) then the first | |
# element is assumed to be the attribute being validated and the second is the name of that validation. | |
%w(validate validate_on_create validate_on_update).each do |method| | |
class_eval <<-EOC, __FILE__, __LINE__ | |
def #{method}_with_single_attr_validations(*methods, &block) | |
methods.map! do |method| | |
if method.is_a?(Array) | |
attr, validation_name = method | |
method = "validate_"+method.map(&:to_s).join("_").to_sym | |
(validations[attr.to_s] ||= {})[validation_name.to_s] = method.to_s | |
end | |
method | |
end | |
#{method}_without_single_attr_validations(*methods, &block) | |
end | |
alias_method_chain :#{method}, :single_attr_validations | |
EOC | |
end | |
end | |
# Run a single validation on a single attribute | |
def valid_by?(name, attr) | |
name = name.to_s | |
attr = attr.to_s | |
num_errors = self.errors_on(attr).size | |
unless validations_for_attr = self.class.validations[attr] | |
raise "No validations have been set on the '#{attr}' attribute!" | |
end | |
#unless validation_method = [VALIDATION_METHODS[name], "#{attr}_#{name}", name].first {|method| respond_to?(method) || Entry.respond_to?(method) } | |
# raise "#{self.class} does not respond to '#{validation_method}'!" | |
#end | |
validation_method = VALIDATION_METHODS[name] || name | |
unless block_or_symbol = validations_for_attr[validation_method] | |
raise "No validation '#{validation_method}' set on #{attr}!" | |
end | |
(block_or_symbol.is_a?(Symbol) || block_or_symbol.is_a?(String)) ? send(block_or_symbol) : block_or_symbol.call(self, attr) | |
# no real way to tell whether the validation succeeded or failed except for this | |
self.errors_on(attr).size == num_errors | |
end | |
# This lets you say e.g. entry.title_valid_by?(:presence) | |
def method_missing_with_single_attr_validations(name, *args) | |
name = name.to_s | |
if name =~ /^(.+)_valid_by\?$/ | |
raise "Must pass an attribute to a _valid_by? method" unless args.size == 1 | |
valid_by?(args.first, $1) | |
else | |
method_missing_without_single_attr_validations(name, *args) | |
end | |
end | |
alias_method_chain :method_missing, :single_attr_validations | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment