如何验证Rails中的日期?


92

我想在Ruby on Rails中验证模型中的日期,但是,到达它们的模型时,天,月和年的值已经转换为错误的日期。

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

我希望能够在我的模型中进行此验证。有什么办法可以解决,还是我要彻底解决这个问题?

我找到了讨论问题的“ 日期验证 ”,但从未解决。


4
这个问题目前是Google进行Rails日期验证的最佳结果之一。您可能像我一样在第一次通过时就错过了它:所选答案的重要部分是validates_timeliness gem。我什至没有读完其余的答案,因为我发现了其他地方的validates_timeliness,而且gem不需要我编写任何自定义代码就可以满足我的所有需求。
杰森·斯威特

Answers:


61

我猜您正在使用date_select帮助程序来生成日期标签。您可以执行的另一种方法是在日,月,年字段中使用选择表单帮助器。像这样(我使用的示例是created_at date字段):

<%= 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 %>

然后在模型中验证日期:

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

如果您正在寻找插件解决方案,请查看validates_timeliness插件。它的工作方式如下(来自github页面):

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 

可用的验证方法列表如下:

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.

建议的解决方案对我不起作用,我使用的是Rails 2.3.5
Roger 2010年

我重写了它以使其更清洁,并针对Rails 2.3.5进行了测试,这确实对我有用。
朱Chu

嗨,杰克,我已经尝试过您的解决方案,它对我来说可以添加attr_accessible:day,:month,:year。这对我来说没有意义...如果您有任何想法,谢谢!
benoitr 2011年

在Rails 3中,我使用的是github.com/adzap/validates_timeliness,它很棒。
杰森·斯威特

validates_timeliness插件/宝石是非常好的使用。很有魅力,并使我的代码更小。谢谢你的提示。
mjnissim 2012年

20

使用慢性宝石:

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

3
在此特定示例中,我不得不使用Chronic.parse(from_date_before_type_cast)以获得字符串值输入。否则,to_date因为该字段是一个datetime字段,所以Rails会使用字符串类型转换字符串。
加尔文2012年

18

如果要与Rails 3或Ruby 1.9兼容,请尝试使用date_validator gem。


我刚试过 花了2分钟全部时间来安装并添加验证!
jflores

11

Active Record为您提供_before_type_cast了在类型转换之前包含原始属性数据的属性。这对于返回带有预类型转换值的错误消息或仅进行类型转换后无法执行的验证很有用。

我会回避Daniel Von Fange重写访问器的建议,因为在访问器中进行验证会稍微改变访问器协定。Active Record具有明确针对这种情况的功能。用它。


4

由于您需要在将日期字符串转换为模型中的日期之前对其进行处理,因此我将覆盖该字段的访问器

假设您的日期字段为published_date。将此添加到您的模型对象:

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

我不知道是否会为此目的使用它,但这是一个很好的建议。
丹·罗森斯塔克

3

这里有点晚了,但是要感谢“ 如何验证日期? ”,我设法编写了这个验证器,希望对某些人有用:

在你里面 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

1

这是一个非临时性的答案。

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

1
这总是返回false:"1/1/2013".is_a?(Date)-日期来自用户输入,因此将其作为字符串输入。我必须首先将其解析为日期?
Mohamad 2013年

正确。Date.parse("1/1/2013").is_a?(Date),但您可能会发现,如果根本不解析它,它也可能不是日期。
2013年

1
需要挽救参数错误...啊,如果它只是自动解析字符串,那真是太棒了...哦,好了,这是宝石的。
Mohamad 2013年

0

您可以像这样验证日期和时间(如果使用自定义选择,则可以在控制器中某个可以访问参数的方法中)...

# 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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.