ActiveRecord Validations are a well-known and widely used functionality of Rails. It provides handy methods like presence
, uniqueness
, numericality
, etc., to validate an attribute's data.
Let's use these validation methods to validate our Event
model attributes.
# == Schema Information
#
# Table name: events
#
# id :integer
# title :string
# start_date :date
# end_date :date
# participant_count :integer
# created_at :datetime
# updated_at :datetime
class Event < ApplicationRecord
validates :start_date, presence: true
validates :end_date, presence: true
end
Here we have added presence
check on start_date
and end_date
. So if any one of the fields is nil
, Rails will throw an ActiveRecord::RecordInvalid
error as below:
Loading development environment (Rails 7.0.0)
3.0.0 :001 > event = Event.new(title: 'Event 1', start_date: nil, end_date: nil)
=> #<Event id: nil, title: "Event 1", start_date: nil, end_date: nil, created_at: nil, updated_at: nil>
3.0.0 :002 > event.validate!
Traceback (most recent call last):
1: from (irb):2:in `<main>'
ActiveRecord::RecordInvalid (Validation failed: Start date can't be blank, End date can't be blank)
Well, that was an easy check, and using ActiveRecord validation made it effortless for us.
Now let's say we also need validation on the end_date
attribute to be always greater than the start_date
.
Before Rails 7
For comparison of dates, there is no in-built validator provided by Rails. So to achieve this with Rails < 7
we have to write our own custom validator as below:
class Event < ApplicationRecord
validates :start_date, presence: true
validates :end_date, presence: true
validate :end_date_is_after_start_date
private
def end_date_is_after_start_date
if end_date < start_date
errors.add(:end_date, 'cannot be before the start date')
end
end
end
Loading development environment (Rails 5.2.6)
2.6.6 :001 > event = Event.new(title: 'Event 1', start_date: Date.today, end_date: Date.yesterday)
=> #<Event id: nil, title: "Event 1", start_date: "2021-11-14", end_date: "2021-11-13", created_at: nil, updated_at: nil>
2.6.6 :002 > event.validate!
Traceback (most recent call last):
1: from (irb):2:in `<main>'
ActiveRecord::RecordInvalid (Validation failed: End date cannot be before the start date)
With Rails >= 7
Rails 7 adds ComparisonValidator to allow the comparison of dates without using a custom validator or any external gem. Let's rewrite the end_date
validation using ComparisonValidator.
class Event < ApplicationRecord
validates :start_date, presence: true
validates :end_date, presence: true
validates :end_date, comparison: { greater_than: :start_date }
# OR
validates_comparison_of :end_date, greater_than: :start_date
end
Loading development environment (Rails 7.0.0)
3.0.0 :001 > event = Event.new(title: 'Event 1', start_date: Date.today, end_date: Date.yesterday)
(2.7ms) SELECT sqlite_version(*)
=> #<Event id: nil, title: "Event 1", start_date: "2021-11-14", end_date: "2021-11-13", created_at: nil, updated_at: nil>
3.0.0 :002 > event.validate!
Traceback (most recent call last):
1: from (irb):2:in `<main>'
ActiveRecord::RecordInvalid (Validation failed: End date must be greater than 2021-11-14)
ComparisonValidator
provides a lot of comparisons option like greater_than
, greater_than_or_equal_to
, equal_to
, less_than
, less_than_or_equal_to
and other_than
. Each option can accept a value
, proc
, or symbol
.
Can we combine multiple compare options?
Yes! we can use one or more compare options at the same time.
Let's say along with ensuring end_date
is always greater than start_date
, we also want to validate that the end_date
is not today's date.
class Event < ApplicationRecord
validates :start_date, presence: true
validates :end_date, presence: true
validates_comparison_of :end_date, greater_than: :start_date, other_than: Date.today
end
Is ComparisonValidator only for date comparison?
No!, ComparisonValidator
allows comparing numeric
, date
as well as string
values.
class Event < ApplicationRecord
validates_comparison_of :participant_count, greater_than_or_equal_to: 2
end
Final Thoughts
ComparisonValidator
is definitely a great addition to ActiveRecord as it will allow us to compare and validate attribute data effortlessly. I hope this article has given you a good idea about this new validator introduced with Rails 7.
Thank you for reading! ❤️