How do I validate a date in rails?

Solution 1:

I'm guessing you're using the date_select helper to generate the tags for the date. Another way you could do it is to use select form helper for the day, month, year fields. Like this (example I used is the created_at date field):

<%= f.select :month, (1..12).to_a, selected: @user.created_at.month %>
<%= f.select :day, (1..31).to_a, selected: @user.created_at.day %>
<%= f.select :year, ((Time.now.year - 20)..Time.now.year).to_a, selected: @user.created_at.year %>

And in the model, you validate the date:

attr_accessor :month, :day, :year
validate :validate_created_at

private

def convert_created_at
  begin
    self.created_at = Date.civil(self.year.to_i, self.month.to_i, self.day.to_i)
  rescue ArgumentError
    false
  end
end

def validate_created_at
  errors.add("Created at date", "is invalid.") unless convert_created_at
end

If you're looking for a plugin solution, I'd checkout the validates_timeliness plugin. It works like this (from the github page):

class Person < ActiveRecord::Base
  validates_date :date_of_birth, on_or_before: lambda { Date.current }
  # or
  validates :date_of_birth, timeliness: { on_or_before: lambda { Date.current }, type: :date }
end 

The list of validation methods available are as follows:

validates_date     - validate value as date
validates_time     - validate value as time only i.e. '12:20pm'
validates_datetime - validate value as a full date and time
validates          - use the :timeliness key and set the type in the hash.

Solution 2:

Using the chronic gem:

class MyModel < ActiveRecord::Base
  validate :valid_date?

  def valid_date?
    unless Chronic.parse(from_date)
      errors.add(:from_date, "is missing or invalid")
    end
  end

end

Solution 3:

If you want Rails 3 or Ruby 1.9 compatibility try the date_validator gem.

Solution 4:

Active Record gives you _before_type_cast attributes which contain the raw attribute data before typecasting. This can be useful for returning error messages with pre-typecast values or just doing validations that aren't possible after typecast.

I would shy away from Daniel Von Fange's suggestion of overriding the accessor, because doing validation in an accessor changes the accessor contract slightly. Active Record has a feature explicitly for this situation. Use it.