Saturday, November 17, 2012

Rails 3: Improve :presence validation

In Rails 3.0 (and probably in modern versions) you can ensure the validity of your models by using validations . One I use is the :presence , to make an object invalid if a variable is not present.

Example:

class Car < ActiveRecord::Base
  belongs_to :user
  validates :user, :presence => true
  ...
end

The problem comes when you investigate how validation is made. Each time we save the validated object is got from the database (*). Something simple as:


car = Car.last
car.save

will hit the database checking if user exists.

Solution: build a custom Validator and use it:


class SexyForeignKeyValidator < ActiveModel::EachValidator
  #http://guides.rubyonrails.org/active_record_validations_callbacks.html#performing-custom-validations
  def validate_each(record, attribute, value)
     return if value.present?
     relation_name = attribute.to_s.gsub(/_id$/,'')
     unless record.send(relation_name)
        record.errors.add relation_name.to_sym, :blank
     end
  end
end

The tricky part is to check for the foreign_key BUT if this is not already set (on create), check for the relation.
This validation run on the foreign key, so we modify the code in the model.


class Car < ActiveRecord::Base
  belongs_to :user
  validates :user_id, :sexy_foreign_key => true
  ...
end

One last note. The code for the validator must be included in some path that is automatically loaded ( read this)

Profit!


(*) Note that this is a very particular case, when we check the presence of a relation. Checking for the foreign_key user_id won't work, since on creation, this foreign_key can be null

No comments:

Post a Comment