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.
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.