Samuel Mullen What could possibly go wrong?

Validating Presence of Associations and Foreign Keys in Rails

by Samuel Mullen

Posted on Dec 31, 2013


The Problem

When working with ActiveRecord models, there are occasions where using either a foreign key or an object to denote an association is desirable. It may be the only available information about the foreign record is the key, or it may be that only a “new” (i.e. not persisted) object has been created. To ensure the association is present, you need to validate the presence of both the association (i.e. the Object) and the foreign key.

Assuming we have a User model with a has_many association to a Post model, the solution might look something like this:

class Post < ActiveRecord::Base
  belongs_to :user, :inverse_of => :posts
  
  validates :user, :presence => {:if => proc{|o| o.user_id.blank? }}
  validates :user_id, :presence => {:if => proc{|o| o.user.blank? }}
end

Here, we’re validating the existence of the User association: first by checking the presence of the object if the foreign key is blank; second by checking the presence of the foreign key if the User association is blank.

This isn’t a great solution, because it results in two error messages if the validation fails.

A New Validation

A better solution is to create a validator. To do that in Rails, the validator file must be created under lib/validators, and the filename must be the snake case version of the class name (i.e. ExistenceValidator => existence_validator.rb).

class ExistenceValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    if value.blank? && record.send("#{attribute}_id".to_sym).blank?
      record.errors[attribute] << I18n.t("errors.messages.existence")
    end
  end
end

lib/validators/existence_validator.rb

As you can see, we subclass ActiveModel::EachValidator and override the validate_each method.

Inside the method, we add to the errors list if either the value of what we are testing is #blank? or if the foreign key is #blank? Just add the desired error message to your config/locales/en.yml file (adjust for language).

en:
  errors:
    messages:
      existence: "must exist as an object or foreign key"

config/locales/en.yml

Aside: #send

The above solution may be a little tricky if you’re not too familiar with metaprogramming in Ruby, so we’ll look at it a little more closely here.

record.send("#{attribute}_id".to_sym).blank?

In Ruby, the #send method takes a symbol as its first argument, and it “sends” that symbol as a message to an object, (i.e. it calls a method of the same name as the symbol).

We are defining the message to be sent with the "#{attribute}_id".to_sym bit – in our use case, it will end up being user_id. That message gets sent to the model instance the validation is defined within and determined if it is blank?.

When fully translated, it looks like this:

record.user_id.blank?

Usage

Now that we’ve defined our new validation, let’s use it. We can remove the two presence validators we had before and replace them with the more succinct existence validator.

class Post < ActiveRecord::Base
  belongs_to :user, :inverse_of => :posts
  
  validates :user, :existence => true
end

Post model using new validator

Where It Doesn’t Work

The solution is pretty good, but it assumes the foreign key is the same name as the association, only with an appended _id (e.g. user and user_id).

For the occasion when you need to get around this, you’ll want to create a sort of “in class” validator for your odd association.

class Post < ActiveRecord::Base
  belongs_to :author, :class_name => User, :inverse_of => :posts

  validate :must_have_author

  private

  def must_have_author
    if author.blank? && user_id.blank?
      self.errors.add(:author, I18n.t("errors.messages.existence")) 
    end
  end
end

Post model using specific validator

At this point you should have a solution for 90% of what you’re going to run into with regard to validating associations. If you see something I missed, or if there’s a way I can improve anything above, please leave a comment and I’ll update the post accordingly.

Read More

Validating Booleans

by Samuel Mullen

Posted on Jun 14, 2012


I ran into an instance today wherein I needed to validate that a boolean field was either true or false and not null. I tried using validates :fieldname, :presence => true, but since :presence uses #blank? under the hood, it was reading false as not being present. (Why is false considered blank?)

Anyway, I needed a validator to test whether an attribute was either true or false and I couldn’t find anything among the standard validators, so I wrote my own.

Just plop this file in your app’s lib/validators directory.

class TruthinessValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value == true || value == false
      record.errors[attribute] << "must be true or false"
    end
  end
end

In your model, add the validation like so:

class SomeModel < ActiveRecord::Base
  ...

  validates :field_name, :truthiness => true

  ...
end

For more information on writing validators, see Getting Started with Custom Rails3 Validators.

Read More

About me


I live in the greater Kansas City area with my beautiful wife, our two great kids, and our dog. I've been programming using Open Source technologies since '97 and I'm currently an independent software developer specializing in Ruby on Rails and iOS. I am for hire.

Freelancing Digest


The Freelancing Digest is a curated newsletter aimed specifically at freelancers. Delivered on the 1st and 15th of the month, each issues contains links to some of the best articles on the web to help you establish and grow your freelancing business.