Reputation: 879
I have an Active Record model that contains attributes: expiry_date. How do I go about validating it such that it is after today(present date at that time)? I am totally new to Rails and ruby and I couldn't find a similar question answering exactly this?
I am using Rails 3.1.3 and ruby 1.8.7
Upvotes: 45
Views: 39692
Reputation: 101
I gathered a few things from the other posts to get my answer, but I think my result may be a little more straight forward than creating a separate validator method or class, which I think we can get around for a relatively simple validation.
proc
so that it would load the time when the validation is run and not when the class is loaded. There were also some suggestions that gave a lambda (->(data)
), but this requires you to act like there is an argument when you don't actually need one.if: :changed?
so that there's only a validation error if something about the record was being updated. If you wanted to do a specific field or even just the timestamp field, you can use the rails method by combining the field name and changed?
- ex. :my_cool_timestamp_attribute_changed?
... is not included in the list
.on: :update
. The rails default is to validate on both create and update. validates(
:my_cool_timestamp_attribute,
inclusion: {
in: proc { Time.current.. },
message: "must be in the future when updating",
if: :changed?
},
on: :update,
)
Upvotes: 2
Reputation: 16373
In rails 4+ there are future?
and past?
methods for DateTime
objects, so a simpler answer is
class Invoice < ActiveRecord::Base
validate :expiration_date_cannot_be_in_the_past
def expiration_date_cannot_be_in_the_past
if expiration_date.present? && expiration_date.past?
errors.add(:expiration_date, "can't be in the past")
end
end
end
Upvotes: 4
Reputation: 277
The simplest and working solution is to use the in-built validation from Rails. Just validates it like that:
validates :expiry_date, inclusion: { in: (Date.today..Date.today+5.years) }
Upvotes: 10
Reputation: 26949
I took @dankohn answer, and updated to be I18n ready. I also removed the blank
test, because that's not the responsibility of this validator, and can easily be enabled by adding presence: true
to the validates call.
The updated class, now named in_future
, which I think is nicer than not_in_past
class InFutureValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors.add(attribute, (options[:message] || :in_future)) unless in_future?(value)
end
def in_future?(date)
date.present? && date > Time.zone.today
end
end
Now add the in_future
key to your localization file.
For all fields under errors.messages.in_future
, e.g. for Dutch:
nl:
errors:
messages:
in_future: 'moet in de toekomst zijn'
Or per field under activerecord.errors.models.MODEL.attributes.FIELD.in_future
, e.g. for the end_date
in a Vacancy
model in Dutch:
nl:
activerecord:
errors:
models:
vacancy:
attributes:
end_date:
in_future: 'moet in de toekomst zijn'
Upvotes: 6
Reputation: 115511
Your question is (almost) exactly answered in the Rails guides.
Here's the example code they give. This class validates that the date is in the past, while your question is how to validate that the date is in the future, but adapting it should be pretty easy:
class Invoice < ActiveRecord::Base
validate :expiration_date_cannot_be_in_the_past
def expiration_date_cannot_be_in_the_past
if expiration_date.present? && expiration_date < Date.today
errors.add(:expiration_date, "can't be in the past")
end
end
end
Upvotes: 74
Reputation: 34327
Here's the code to set up a custom validator:
#app/validators/not_in_past_validator.rb
class NotInPastValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
if value.blank?
record.errors.add attribute, (options[:message] || "can't be blank")
elsif value <= Time.zone.today
record.errors.add attribute,
(options[:message] || "can't be in the past")
end
end
end
And in your model:
validates :signed_date, not_in_past: true
Upvotes: 19