¿Cómo valido una fecha en rieles?

92

Quiero validar una fecha en mi modelo en Ruby on Rails, sin embargo, los valores de día, mes y año ya se han convertido en una fecha incorrecta cuando llegan a mi modelo.

Por ejemplo, si introduzco el 31 de febrero de 2009 en mi vista, cuando lo uso Model.new(params[:model])en mi controlador, lo convierte a "3 de marzo de 2009", que mi modelo ve como una fecha válida, que es, pero es incorrecta.

Me gustaría poder hacer esta validación en mi modelo. ¿Hay alguna manera de que pueda, o estoy haciendo esto completamente mal?

Encontré esta " Validación de fecha " que analiza el problema, pero nunca se resolvió.

Megamug
fuente
4
Esta pregunta es actualmente uno de los principales resultados de Google para la validación de la fecha de Rails. Es posible que se lo pierda en la primera pasada como lo hice yo: la parte importante de la respuesta seleccionada es la gema validates_timeliness. Ni siquiera leí el resto de la respuesta porque descubrí validates_timeliness en otro lugar, y esa gema hace todo lo que necesito sin que tenga que escribir ningún código personalizado.
Jason Swett

Respuestas:

61

Supongo que está utilizando el date_selectasistente para generar las etiquetas para la fecha. Otra forma de hacerlo es utilizar el asistente de formulario de selección para los campos de día, mes y año. Así (el ejemplo que utilicé es el campo 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 %>

Y en el modelo, valida la fecha:

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

Si está buscando una solución de complemento, verificaría el complemento validates_timeliness . Funciona así (desde la página de 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 

La lista de métodos de validación disponibles es la siguiente:

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.
Jack Chu
fuente
La solución sugerida no me funciona y estoy usando Rails 2.3.5
Roger
Lo reescribí para que fuera más limpio y lo probé contra Rails 2.3.5 y esto funciona para mí.
Jack Chu
Hola Jack, probé tu solución, me funciona agregando attr_accessible: day,: month,: year. Lo cual no tiene sentido para mí ... ¡Gracias si tienes alguna idea!
benoitr
En Rails 3, estoy usando github.com/adzap/validates_timeliness y es increíble.
Jason Swett
El complemento / gema validates_timeliness es realmente agradable de usar. Funciona de maravilla y mantiene mi código más pequeño. Gracias por el consejo.
mjnissim
20

Usando la gema crónica:

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
Roger
fuente
3
En este ejemplo específico, tuve que usar Chronic.parse(from_date_before_type_cast)para obtener la entrada del valor de la cadena. De lo contrario, Rails encasillaría la cadena con to_dateya que el campo era un datetimecampo.
Calvin
18

Si desea compatibilidad con Rails 3 o Ruby 1.9, pruebe la gema date_validator .

Txus
fuente
Solo probé esto. ¡Tomó 2 minutos instalar y agregar validación!
jflores
11

Active Record le proporciona _before_type_castatributos que contienen los datos de atributos sin procesar antes de encasillar. Esto puede ser útil para devolver mensajes de error con valores pre-encasillados o simplemente para hacer validaciones que no son posibles después de encasillar.

Me alejaría de la sugerencia de Daniel Von Fange de anular el acceso, porque hacer la validación en un acceso cambia ligeramente el contrato del acceso. Active Record tiene una función explícita para esta situación. Úselo.

Joshuacronemeyer
fuente
4

Dado que necesita manejar la cadena de fecha antes de que se convierta a una fecha en su modelo, anularía el descriptor de acceso para ese campo

Digamos que su campo de fecha es published_date. Agregue esto a su objeto modelo:

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
Daniel Von Fange
fuente
No sé si usaré esto para este propósito, pero es una gran sugerencia.
Dan Rosenstark
3

Un poco tarde aquí, pero gracias a " ¿Cómo valido una fecha en rieles? " Logré escribir este validador, espero que sea útil para alguien:

Dentro de tu 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
fuente
1

Aquí hay una respuesta no crónica ...

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
Viaje
fuente
1
Esto siempre devuelve falso: "1/1/2013".is_a?(Date)- La fecha proviene de la entrada del usuario, por lo que se alimenta como una cadena. ¿Primero tendría que analizarlo como una fecha?
Mohamad
Correcto. Date.parse("1/1/2013").is_a?(Date), pero puede ver que si no analiza en absoluto, probablemente tampoco sea una fecha.
Viaje
1
Necesito rescatar el error del argumento ... ahh, hubiera sido increíble si solo hubiera analizado la cadena automáticamente ... bueno, hay gemas para eso.
Mohamad
0

Puede validar la fecha y la hora así (en un método en algún lugar de su controlador con acceso a sus parámetros si está utilizando selecciones personalizadas) ...

# 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
King'ori Maina
fuente
-3

¿Has probado el validates_date_timecomplemento?

Ryan Duffield
fuente
Si bien este enlace puede responder la pregunta, es mejor incluir aquí las partes esenciales de la respuesta y proporcionar el enlace como referencia. Las respuestas de solo enlace pueden dejar de ser válidas si cambia la página enlazada.
Pinal
Me gusta más mi respuesta. Dos personas están de acuerdo.
Ryan Duffield
4
@Pinal El enlace está realmente muerto ahora.
Wayne Conrad