Como faço para validar uma data no rails?

92

Quero validar uma data em meu modelo em Ruby on Rails, porém, os valores de dia, mês e ano já estão convertidos em uma data incorreta no momento em que chegam ao meu modelo.

Por exemplo, se eu inserir 31 de fevereiro de 2009 em minha visualização, quando eu usar Model.new(params[:model])em meu controlador, ele o converterá em "3 de março de 2009", que meu modelo então vê como uma data válida, o que é, mas está incorreta.

Gostaria de poder fazer essa validação no meu modelo. Existe alguma maneira de eu poder, ou estou fazendo isso completamente errado?

Encontrei esta " Validação de data " que discute o problema, mas nunca foi resolvido.

Megamug
fonte
4
Esta questão é atualmente um dos principais resultados do Google para validação de data Rails. Você pode perder isso na primeira passagem, como eu: a parte importante da resposta selecionada é a joia validates_timeliness. Eu nem li o resto da resposta porque descobri sobre validates_timeliness em outro lugar, e aquele gem faz tudo que eu preciso sem que eu tenha que escrever nenhum código customizado.
Jason Swett

Respostas:

61

Suponho que você esteja usando o date_selectauxiliar para gerar as tags para a data. Outra maneira de fazer isso é usar o auxiliar de seleção de formulário para os campos de dia, mês e ano. Assim (o exemplo que usei é o campo de data 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 %>

E no modelo, você valida a data:

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

Se você está procurando uma solução de plug-in, verificaria o plug-in validates_timeliness . Funciona assim (na página do 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 

A lista de métodos de validação disponíveis é a seguinte:

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
fonte
A solução sugerida não funciona para mim e estou usando Rails 2.3.5
Roger
Eu o reescrevi para ficar mais limpo e testei no Rails 2.3.5 e isso funciona para mim.
Jack Chu,
Olá Jack, Tentei sua solução, ela funciona para mim adicionando attr_accessible: day,: month,: year. O que não faz sentido para mim ... Obrigado se você tem alguma idéia!
benoitr,
No Rails 3, estou usando github.com/adzap/validates_timeliness e é incrível.
Jason Swett
O plugin / gem validates_timeliness é muito bom de usar. Funciona muito bem e mantém meu código menor. Obrigado pela dica.
mjnissim
20

Usando a 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
fonte
3
Neste exemplo específico, tive que usar Chronic.parse(from_date_before_type_cast)para obter a entrada do valor da string. Caso contrário, Rails faria um typecast da string com, to_datejá que o campo era um datetimecampo.
Calvin
18

Se você deseja compatibilidade com Rails 3 ou Ruby 1.9, experimente a gem date_validator .

Txus
fonte
Eu apenas tentei isso. Demorou 2 minutos para instalar e adicionar validação!
jflores
11

O Active Record fornece _before_type_castatributos que contêm os dados de atributo brutos antes do typecasting. Isso pode ser útil para retornar mensagens de erro com valores pre-typecast ou apenas fazer validações que não são possíveis após o typecast.

Eu evitaria a sugestão de Daniel Von Fange de substituir o acessador, porque fazer a validação em um acessador altera ligeiramente o contrato do acessador. O Active Record tem um recurso explicitamente para esta situação. Use-o.

Joshuacronemeyer
fonte
4

Já que você precisa lidar com a string de data antes que ela seja convertida em uma data em seu modelo, eu substituiria o acessador para esse campo

Digamos que seu campo de data seja published_date. Adicione isto ao seu 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
fonte
Não sei se vou usar para esse fim, mas é uma ótima sugestão.
Dan Rosenstark
3

Um pouco tarde aqui, mas graças a “ Como faço para validar uma data no rails? ” Consegui escrever este validador, espero que seja útil para alguém:

Dentro do seu 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
fonte
1

Aqui está uma resposta não 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
Viagem
fonte
1
Isso sempre retorna falso: "1/1/2013".is_a?(Date)- A data vem da entrada do usuário, portanto, está sendo alimentada como uma string. Eu teria que analisar como uma data primeiro?
Mohamad
Corrigir. Date.parse("1/1/2013").is_a?(Date), mas você pode ver que, se não analisar, provavelmente também não é uma data.
Viagem de
1
É preciso resgatar o erro de argumento ... ahh, teria sido incrível se ele apenas analisasse a string automaticamente ... bem, existem gemas para isso.
Mohamad
0

Você pode validar a data e hora dessa forma (em um método em algum lugar em seu controlador com acesso aos seus parâmetros se você estiver usando seleções 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
fonte
-3

Você já experimentou o validates_date_timeplug-in?

Ryan Duffield
fonte
Embora este link possa responder à pergunta, é melhor incluir as partes essenciais da resposta aqui e fornecer o link para referência. As respostas somente com link podem se tornar inválidas se a página vinculada mudar.
Pinal de
Eu gosto mais da minha resposta. Duas pessoas concordam.
Ryan Duffield
4
@Pinal O link está realmente morto agora.
Wayne Conrad