如何验证 Rails 中的日期?

我想在 Ruby on Rails 中验证我的模型中的日期,但是,当日期、月份和年份的值到达我的模型时,它们已经被转换成了不正确的日期。

例如,如果我在视图中输入2009年2月31日,当我在控制器中使用 Model.new(params[:model])时,它会将其转换为“ March 3rd 2009”,然后我的模型将其视为有效日期,它是有效的,但它是不正确的。

我希望能够在我的模型中进行验证。有没有可能我可以,或者我是不是完全搞错了?

我发现这个“ 日期确认”讨论的问题,但它从来没有得到解决。

113727 次浏览

Since you need to handle the date string before it is converted to a date in your model, I'd override the accessor for that field

Let's say your date field is published_date. Add this to your model object:

def published_date=(value)
# do sanity checking here
# then hand it back to rails to convert and store
self.write_attribute(:published_date, value)
end

Have you tried the validates_date_time plug-in?

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.

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

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

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.

Here's a non-chronic answer..

class Pimping < ActiveRecord::Base


validate :valid_date?


def valid_date?
if scheduled_on.present?
unless scheduled_on.is_a?(Time)
errors.add(:scheduled_on, "Is an invalid date.")
end
end
end

You can validate the date and time like so (in a method somewhere in your controller with access to your params if you are using custom selects) ...

# Set parameters
year = params[:date][:year].to_i
month = params[:date][:month].to_i
mday = params[:date][:mday].to_i
hour = params[:date][:hour].to_i
minute = params[:date][:minute].to_i


# Validate date, time and hour
valid_date    = Date.valid_date? year, month, mday
valid_hour    = (0..23).to_a.include? hour
valid_minute  = (0..59).to_a.include? minute
valid_time    = valid_hour && valid_minute


# Check if parameters are valid and generate appropriate date
if valid_date && valid_time
second = 0
offset = '0'
DateTime.civil(year, month, mday, hour, minute, second, offset)
else
# Some fallback if you want like ...
DateTime.current.utc
end

A bit late here, but thanks to "How do I validate a date in rails?" I managed to write this validator, hope is useful to somebody:

Inside your model.rb

validate :date_field_must_be_a_date_or_blank


# If your field is called :date_field, use :date_field_before_type_cast
def date_field_must_be_a_date_or_blank
date_field_before_type_cast.to_date
rescue ArgumentError
errors.add(:birthday, :invalid)
end

I recommend this method instead

  validate :birthday_must_be_a_date


def birthday_must_be_a_date
birthday&.to_date || errors.add(:birthday, :blank)
rescue Date::Error
errors.add(:birthday, :invalid)
end