home icon contact icon rss icon

By: Matt Lins

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 &gt; 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