Custom Attribute Validators with Rails 4+

December 20, 2016

Custom Attribute Validators With Rails 4+

As you build more Rails applications, you’ll find it’s helpful to ensure that you guarantee the integrity of your data. Let’s suppose you are saving a web page URL in your database.

You may want it in a particular format. For example, you may want your URL to begin with “https://” or “http://”. Custom attribute validators are one way to do this.

Ruby logo on railroad tracks

A specific format for URLs is an actual problem I had to solve on the job. This motivated me to look into Rails validations.

In general, validations in Rails ensure that only valid data is saved into the database.

To understand validations used by ActiveRecord, reading the documentation can be helpful. In general, model-level validations occur before the data is saved into the database.

It’s helpful to know which Rails methods trigger validations

The following methods called on an ActiveRecord model fire off the Rails validation mechanism. The bang methods (those with an !) trigger an exception if a record is invalid.

  • create
  • create!
  • save
  • save!
  • update
  • update!

Other methods such as touch and update_all skip validations and are covered in the documentation.

ActiveModel::EachValidator

If you need to add custom validation for specific model attributes, Rails gives you a handy way with ActiveModel::EachValidator. Your custom validation class should inherit from ActiveModel::EachValidator and take 3 arguments: record, attribute, and value.

Example: Trying to validate an URL

The following example walks through a similar problem I solved on the job.

I started with a WebPage model with a validation on its URL with something like the below example.

class WebPage < ActiveRecord::Base
  validates :web_page_url, format: { with: /\A((http|https):\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\z/ix, message: "invalid url" }
end

Then someone at work suggested I look at custom attribute validations. Like its namesake suggests, custom validations allow you to customize validation behavior on specific attributes.

Switching to custom validations

Let’s say we have a WebPage model with a web_page_url attribute. We want to validate that only URLs of the form facebook.com, https://www.facebook.com/ and http://www.facebook.com/ are the only types of URLs that can be saved.

class UrlValidator < ActiveModel::EachValidator
  def validate_each(record, attribute, value)
    unless value =~ /\A((http|https):\/\/)?[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?\z/ix || options[:allow_blank] == true
      record.errors[attribute] << "There is a problem with your URL."
    end
  end
end

class WebPage < ActiveRecord::Base
  validates :web_page_url, url: true, allow_blank: true
end

Note that I have url: true in the validates call to tell Rails to use the UrlValidator class. In the UrlValidator class, I overwrite the validate_each method.

If you compare the example above to the one in the documentation, you’ll notice I allow a blank field to be saved to the web_page_url database column through the use of the options hash.

Summary

If you need a custom validation behavior on a specific Rails model attribute, consider custom attribute validators through the use of ActiveModel::EachValidator.


Profile picture

Written by Bruce Park who lives and works in the USA building useful things. He is sometimes around on Twitter.