Как проверить дату в рельсах?

Я хочу проверить дату в моей модели в Ruby on Rails, однако значения дня, месяца и года уже преобразуются в неправильную дату к тому времени, когда они достигают моей модели.

Например, если я ввожу 31 февраля 2009 года в моем представлении, когда я использую Model.new(params[:model]) в своем контроллере, он преобразует его в «3 марта 2009 года», который моя модель затем видит как действительную дату, что так и есть, но это неверно.

Я хотел бы иметь возможность провести эту проверку в моей модели. Есть ли способ, которым я могу это сделать, или я ошибаюсь?

Я нашел эту « проверку даты », в которой обсуждается проблема, но она так и не была решена.

Ответов (9)

Решение

Я предполагаю, что вы используете date_select помощника для создания тегов для даты. Другой способ сделать это - использовать помощник по выбору формы для полей дня, месяца, года. Вот так (я использовал поле даты created_at):

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

Использование хронического драгоценного камня:

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

Если вам нужна совместимость с Rails 3 или Ruby 1.9, попробуйте гем date_validator .

Вот не хронический ответ ..

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

Поскольку вам нужно обработать строку даты, прежде чем она будет преобразована в дату в вашей модели, я бы переопределил метод доступа для этого поля.

Допустим, у вас есть поле даты 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

Вы пробовали validates_date_timeплагин?

Active Record предоставляет вам _before_type_cast атрибуты, которые содержат необработанные данные атрибутов перед приведением типов. Это может быть полезно для возврата сообщений об ошибках со значениями до приведения типов или просто для выполнения проверок, которые невозможны после приведения типов.

Я бы уклонился от предложения Даниэля фон Фанге переопределить аксессор, потому что выполнение проверки в аксессоре немного меняет контракт аксессора. Active Record имеет особенность специально для этой ситуации. Используй это.

Вы можете проверить дату и время таким образом (в методе где-то в вашем контроллере с доступом к вашим параметрам, если вы используете пользовательские выборки) ...

# 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

Немного поздно, но благодаря « Как мне проверить дату в рельсах? » Мне удалось написать этот валидатор, надеюсь, кому-то пригодится:

Внутри твоего 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