February 8, 2019
•Last updated November 5, 2023
Understanding Ruby on Rails ActiveRecord Validations
Data is a great thing but the wrong data can be very very bad. Luckily, if you're a Ruby on Rails programmer you have a nice API of ActiveRecord validation helpers at your disposal.
This guide is an exploration of common and not so common ActiveRecord validations in a given Ruby on Rails application. Use it to help keep your databases happy!
Why use validations?
To ensure only valid data is saved to your database. On top of clientside validation, model-level validations provide an extra layer of security for any and all form fields within a Ruby on Rails application.
You can provide validations in a variety of levels but all have their strengths and weaknesses.
- Database constraints - Unique constraints at the database level allows for no duplicate fields or columns if you require. This can go much deeper than just uniqueness. i.e. Default values, non-nil constraints, ranges, etc...
- Client-side validation - These are useful visually and provide better user experience but are often more unreliable. I'd almost always provide a backup plan using this approach as one should.
- Model Level (Rails) - What we are talking about now!
- Controller Level (Rails) - Tempting to use but more difficult to test and maintain. It's a convention/best-practice to keep your controllers as light as possible.
When does validation actually occur?
Straight from the ruby on rails documentation:
There are two kinds of Active Record objects: those that correspond to a row inside your database and those that do not. When you create a fresh object, for example using the
new
method, that object does not belong to the database yet. Once you callsave
upon that object it will be saved into the appropriate database table. Active Record uses thenew_record?
instance method to determine whether an object is already in the database or not.
Creating a new Active Record class grants us an opportunity to use a new_record?
method to figure this all out.
class Article < ApplicationRecord
end
In doing so we can hit rails console
to pinpoint the process.
$ rails console
>> article = Article.new(title: "My Article")
=> #<Article id:nil title: "My Article", created_at: nil, updated_at: nil>
>> article.new_record?
=> true
>> article.save
=> true
>> article.new_record?
=> false
Actually calling save
is what commits it to the database and behind the scenes creates the necessary SQL
statements to do so. As a result, validations are typically run before sending data to the database entirely.
The following methods envoke validations upon use:
create
create!
save
save!
update
update!
The bang versions (e.g.
save!
) raise an exception if the record is invalid. The non-bang versions don't:save
andupdate
returnfalse
, andcreate
just returns the object.
Methods that skip validations
Using any of the following will skip validations entirely. Use with caution on valuable data!
decrement!
decrement_counter
increment!
increment_counter
toggle!
touch
update_all
update_attribute
update_column
update_columns
update_counters
Validation helpers
What I love about Ruby on Rails is the bundled goodness you get for doing pretty hard things. Validation helpers are one of those things that make me feel warm and cozy when deploying a new app out in the wild. This provides a lot of support for a large range of data you may be collecting from the client-side.
Each helper accepts a :on
and :message
option. These options define when the validation should be run and what message should be added to an errors
collection if it fails (you can use these to help inject what's wrong inside your views so the user knows what to change/alter).
There are a ton of validation helpers, below are some of my most used. Please check out the guide on active record validations for more use-cases.
Confirmation
Useful for validating when two text fields need to have the same entry. Think of an email confirmation for example.
class User < ApplicationRecord
validates :email, confirmation: true
end
Now in your view you can have two fields:
<%= text_field :user, :email %>
<%= text_field :user, :email_confirmation %>
One gotcha here is that this validation only performs if the presence
option is set to true in the model as well.
So the User
model gets an update:
class User < ApplicationRecord
validates :email, confirmation: true
validates :email_confirmation, presence: true
end
Exclusion
If you need to reserve any words or make sure a given entry is unique use exclusion
.
class Profile < ApplicationRecord
validates :username, exclusion: { in: %w(username1 username2 username3), message: "%{value} has already been taken." }
end
Notice how I used :message
here as well. This is provided on all validation helpers.
You can also use inclusion
in the opposite manner. See the docs for more info!
Format
Formatting strings are sometimes ideal for your app. You can use regular expressions within validations to alter whatever you're after.
class Plan < ApplicationRecord
validates :plan_type, format: { with: /\A[a-zA-Z]+\z/, message: "Plans can only inlude letters"}
end
Length
I use this one all the time to put a constraint on the character length of a given field.
class User < ApplicationRecord
validates :name, length: { minimum: 5 } # sets a minimum
validates :bio, length: { maximum: 500 } # sets a maximum
validates :handle, length: { in: 5..16 } # provide a range
end
Feel free to personalize the messages with :message
here as well. The following are all available to hook into to customize the message.
wrong_length
too_long
too_short
Example
class User < ApplicationRecord
validates :bio, length: { maximum: 800, too_long: "%{count} characters is the maximum." }
end
Presence
This is probably the most widely used helper in my tool bag. Presence checks that fields are not empty.
class User < ApplicationRecord
validates :name, :email, presence: true
end
You can also test an association exists on models
class Article < ApplicationRecord
belongs_to :user
validates :user, presence: true # looks for a user_id field on the article
end
On the opposing model, you can do the same with one catch called inverse_of
class User < ApplicationRecord
has_many :articles, inverse_of: :user
end
Uniqueness
Maybe you want unique usernames on your app? Uniqueness helps with that.
class User < ApplicationRecord
validates :username, uniqueness: true, { message: "Username already taken" }
end
This actually searches the User
table for existing records to determine uniqueness
You can go further and limit the uniqueness
down to a scope
.
class User < ApplicationRecord
validates :plan, uniqueness: { scope: :year, message: "can only update one per year"}
end
Conditional Validations
Sometimes you only need to validate within given conditions. You can perform conditionals using an approach like the following.
class Purchase < ApplicationRecord
validates :card_number, presence: true, if: :paid_with_card?
def paid_with_card?
payment_type == "card"
end
end
Need to group multiple conditions? You can do that
class User < ApplicationRecord
with_options if: :is_admin? do |admin|
admin.validates :location, length: { minimum: 10 }
admin.validates :job_title, presence: true
end
end
Custom Methods
Sometimes the helpers aren't enough and you might need something more custom. You can create your own methods that do some validations you roll yourself.
class Invoice < ApplicationRecord
validate :active_subscriber, on: :create
def active_customer
errors.add(:customer_id, "is not active")
unless subscriber.active?
end
end
Note here I'm validating this method using the :on
option which lets you determine where to initialize the validation. In this case I chose :create
.
You can also hook into the errors
collection as I have to amend what outputs on the client-side if you need to. We don't have the :message
on custom methods in this case. Read more about errors.
Final Words
Hopefully, validations have opened some ideas for you to make your app more constrained (in a good way). Using Ruby on Rails and all of its facets allows for safe data to enter any database of your choosing. From CSRF tokens to database-level validations, there are endless ways to enhance your app can remain calm about the data coming in.
Shameless Plug!
If you liked this post, I have more videos on YouTube and here on my blog. Want more content like this in your inbox? Subscribe to my newsletter and get it automatically.
Check out my course
☝ Want to learn Ruby on Rails from the ground up? Check out my upcoming course called Hello Rails.
Categories
Collection
Part of the Ruby on Rails collection
Products and courses
-
Hello Hotwire
A course on Hotwire + Ruby on Rails.
-
Hello Rails
A course for newcomers to Ruby on Rails.
-
Rails UI
UI templates and components for Rails.