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

92

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

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

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

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

Мегамуг
источник
4
Этот вопрос в настоящее время является одним из лучших результатов Google по проверке даты Rails. Вы можете пропустить его на первом проходе, как это сделал я: важная часть выбранного ответа - драгоценный камень validates_timeliness. Я даже не прочитал оставшуюся часть ответа, потому что узнал о validates_timeliness где-то еще, и этот гем делает все, что мне нужно, без необходимости писать какой-либо собственный код.
Джейсон Светт

Ответы:

61

Я предполагаю, что вы используете 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.
Джек Чу
источник
Предлагаемое решение не работает для меня, и я использую Rails 2.3.5
Роджер
Я переписал его, чтобы он был чище, и протестировал его на Rails 2.3.5, и это действительно работает для меня.
Джек Чу,
Привет, Джек, я пробовал ваше решение, оно работает для меня, добавляя attr_accessible: day,: month,: year. Что для меня не имеет смысла ... Спасибо, если у вас есть идеи!
Benoitr 07
В Rails 3 я использую github.com/adzap/validates_timeliness, и это здорово.
Джейсон Светт
Validates_timeliness плагин / камень очень приятно использовать. Работает очаровательно и уменьшает мой код. Спасибо за чаевые.
mjnissim
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)для ввода строкового значения. В противном случае Rails привел бы строку to_dateк типу, поскольку поле было datetimeполем.
Calvin
18

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

Txus
источник
Я только что пробовал это. На установку и добавление валидации ушло всего 2 минуты!
jflores
11

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

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

Joshuacronemeyer
источник
4

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

Внутри вашего 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
unmultimedio
источник
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
Даниэль фон Фанге
источник
Не знаю, буду ли я использовать это для этой цели, но это отличное предложение.
Дэн Розенстарк,
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
Верный. Date.parse("1/1/2013").is_a?(Date), но вы могли видеть, что если он вообще не разбирается, вероятно, это также не дата.
Поездка
1
Нужно спасти ошибку аргумента ... ах, было бы здорово, если бы он просто автоматически проанализировал строку ... да ладно, для этого есть драгоценные камни.
Мохамад
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
Кинг'ори Майна
источник
-2

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

Райан Даффилд
источник
Хотя эта ссылка может дать ответ на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными, если ссылка на страницу изменится.
Pinal
Мне больше нравится мой ответ. Два человека согласны.
Райан Даффилд
4
@Pinal Ссылка действительно мертва.
Уэйн Конрад