coding_pleasures
coding_pleasures

Reputation: 879

How to validate the date such that it is after today in Rails?

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

Answers (6)

Scott Gratton
Scott Gratton

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.

  • I used the 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.
  • For me, I didn't want it to throw a validation error if nothing was updated so I also added 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?
  • The error message was a little non specific, so it helped to make a custom one. Without adding the message, it comes back as ... is not included in the list.
  • In my case, I was only worried about updates (and not initial creation) being possibly set in the past, so I have 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

Obromios
Obromios

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

Amit Attri
Amit Attri

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

Koen.
Koen.

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

apneadiving
apneadiving

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

Dan Kohn
Dan Kohn

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

Related Questions