Dissecting Rails Validations - Part II
The loading of the built-in validators
Well, it looks as if my posts will be irrespective of the actual chronology of events involved in validation. At least from a Rails start-to-finish call stack perspective. This post will venture back before those events discussed in Part I. The reason being, I forgot to mention how the Rails built-in validators are loaded and executed.
When your model's class definition is encountered(during initialization), all of the method calls to the built-in validators are ran. Let's say we have a model that looks like this:
1 2 3 |
class Supplier < ActiveRecord::Base validates_uniqueness_of :name end |
When #validates_uniqueness_of is called, it jumps to ActiveRecord::Validations::ClassMethods, which was mixed in with ActiveRecord::Base during initilization earlier. We're not going to look at this method in whole because it's too long. The important part is that it makes use of #validates_each. I'd like to talk about #validates_each because it is the heart of almost all the built-in validators(some can't use it). Here it is:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def validates_each(*attrs) options = attrs.last.is_a?(Hash) ? attrs.pop.symbolize_keys : {} attrs = attrs.flatten # Declare the validation. send(validation_method(options[:on] || :save)) do |record| # Don't validate when there is an :if condition and that condition is false unless options[:if] && !evaluate_condition(options[:if], record) attrs.each do |attr| value = record.send(attr) next if value.nil? && options[:allow_nil] yield record, attr, value end end end end |
After the first couple of lines you see this:
send(validation_method(options[:on] || :save)) do |record| |
This line of code is using the Ruby #send method to make a dynamic call to whatever #validation_method returns. Let's look at it:
1 2 3 4 5 6 7 |
def validation_method(on) case on when :save then :validate when :create then :validate_on_create when :update then :validate_on_update end end |
When you declare your validation in your model, :on is an optional parameter. So, if we only wanted to run our validation on only the creation of records, we could do this:
1 2 3 |
class Supplier > ActiveRecord::Base validates_uniqueness_of :name, :on => :create end |
The #validation_method method will tell the #send method back in #validates_each where to declare the validator. Assuming we pass don't pass anything for the :on parameter it'll be declared by #validate(both create and update). Don't get these methods confused with low-level #validate, #validate_on_create and #validate_on_update of which you can override in your models. These are defined outside of Validations::ClassMethods and they have a different amount of parameters. Lets look at Validations::ClassMethods#validate:
1 2 3 4 |
def validate(*methods, &block) methods << block if block_given? write_inheritable_set(:validate, methods) end |
Validators can be methods, classes or blocks. This method will handle whatever you throw at it and toss it onto the array of validations to be ran whenever #valid? is called. It's using a method called #write_inheritable_set to store the array (read more about it in the inheritable_attributes.rb file).
Now, if you remember correctly from Part I the ActiveRecord::Base#save call is actually aliased to ActiveRecord::Validations#save_with_validation. This method makes a call to ActiveRecord::Validations#valid?. Let's dig into it:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
def valid? errors.clear run_validations(:validate) validate if new_record? run_validations(:validate_on_create) validate_on_create else run_validations(:validate_on_update) validate_on_update end errors.empty? end |
First, it clears the errors object. The errors object is defined by the ActiveRecord::Errors class, which mixes in Enumerable. You can add errors with #add and clear them with #clear etc. Anytime a validator encounters a problem with your model it adds an error. Next, #valid? calls #run_validations with :validate as a parameter(validation_method). Remember, these are validations that are called on both create and update. Continuing, it determines whether the record is new, it calls #run_validations again with the appropriate parameter(validation_method). Lastly, it returns the result of the expression errors.empty?. So, if any problems occurred during #run_validations, then the validator would have added an error to the enumerable causing #valid? to return false. Let's look at #run_validations:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
def run_validations(validation_method) validations = self.class.read_inheritable_attribute(validation_method.to_sym) if validations.nil? then return end validations.each do |validation| if validation.is_a?(Symbol) self.send(validation) elsif validation.is_a?(String) eval(validation, binding) elsif validation_block?(validation) validation.call(self) elsif validation_class?(validation, validation_method) validation.send(validation_method, self) else raise( ActiveRecordError, "Validations need to be either a symbol, string (to be eval'ed), proc/method, or " + "class implementing a static validation method" ) end end end |
It grabs all the validations based on the validation_method passed using the #read_inheritable_attribute (look in the inheritable_attributes.rb file to find out why this is used). Next, it iterates through each of the validations. It does a check to see if the validation is a method, class or a block and executes it appropriately. Remember, if these validations encounter problems with the model data, they'll add an error onto the @errors enumerable causing #valid? to return false. Let's tie it all together with a review of the #save_with_validation method:
1 2 3 4 5 6 7 |
def save_with_validation(perform_validation = true) if perform_validation && valid? || !perform_validation save_without_validation else false end end |
If #valid? returns true(and you didn't pass false to #save), then #save_with_validation makes a call to #save_without_validation, which is actually aliased back to ActiveRecord::Base#save thus completing our save. Remember, ActiveRecord::Base#save is actually aliased to #save_with_validation so that validations can happen. Confused? Well, this is done with #alias_method_chain. Also, you may need to review Part I.
That should do it for another part to this series. If I feel the need to write Part III, we'll probably dive into what each of the built-in validators is actually doing.

Paul said
Apr 15, 2008 @ 11:55 PM
This helped me to debug a weird problem whereby all my validators were being added twice. Somehow my class was being initialized twice because of multiple paths to include its definition. Weird thing: it was only ever twice, even though as I culled the inclusion points there were clearly multiple paths to the inclusion. Another weird thing: it happened reliably on my production machine and sporadically on my development machine - both were running in production mode and both used the same version of rails and ruby. Oh well.Matt Lins said
Apr 15, 2008 @ 11:55 PM
Paul, I glad the article was able to help you. Thanks for the comment!RSS feed for comments on this post
Leave a Comment