Comment valider une date dans les rails?

92

Je souhaite valider une date dans mon modèle dans Ruby on Rails, cependant, les valeurs du jour, du mois et de l'année sont déjà converties en une date incorrecte au moment où elles atteignent mon modèle.

Par exemple, si j'entre le 31 février 2009 à mon avis, lorsque j'utilise Model.new(params[:model])dans mon contrôleur, il le convertit en "3 mars 2009", que mon modèle considère alors comme une date valide, ce qui est le cas, mais c'est incorrect.

J'aimerais pouvoir faire cette validation dans mon modèle. Y a-t-il moyen de le faire ou est-ce que je me trompe complètement?

J'ai trouvé cette " Date de validation " qui traite du problème mais elle n'a jamais été résolue.

Megamug
la source
4
Cette question est actuellement l'un des meilleurs résultats Google pour la validation de la date Rails. Vous pourriez le rater au premier passage comme je l'ai fait: la partie importante de la réponse sélectionnée est la gemme validates_timeliness. Je n'ai même pas lu le reste de la réponse parce que j'ai découvert validates_timeliness ailleurs, et ce joyau fait tout ce dont j'ai besoin sans que j'aie à écrire de code personnalisé.
Jason Swett

Réponses:

61

Je suppose que vous utilisez l' date_selectassistant pour générer les balises pour la date. Une autre façon de le faire est d'utiliser l'assistant de sélection de formulaire pour les champs jour, mois et année. Comme ceci (l'exemple que j'ai utilisé est le champ de date 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 %>

Et dans le modèle, vous validez la date:

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 vous recherchez une solution de plugin, je vérifierais le plugin validates_timeliness . Cela fonctionne comme ceci (à partir de la page 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 liste des méthodes de validation disponibles est la suivante:

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
la source
La solution suggérée ne fonctionne pas pour moi et j'utilise Rails 2.3.5
Roger
Je l'ai réécrit pour être plus propre et l'ai testé avec Rails 2.3.5 et cela fonctionne pour moi.
Jack Chu
Bonjour Jack, j'ai essayé votre solution, cela fonctionne pour moi en ajoutant attr_accessible: day,: month,: year. Ce qui n'a pas de sens pour moi ... Merci si vous avez une idée!
benoitr
Dans Rails 3, j'utilise github.com/adzap/validates_timeliness et c'est génial.
Jason Swett
Le plugin / gem validates_timeliness est vraiment agréable à utiliser. Fonctionne un charme et garde mon code plus petit. Merci pour le conseil.
mjnissim
20

Utilisation de la gemme chronique:

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
la source
3
Dans cet exemple spécifique, j'ai dû utiliser Chronic.parse(from_date_before_type_cast)pour obtenir l'entrée de valeur de chaîne. Sinon, Rails transtypait la chaîne avec to_datepuisque le champ était un datetimechamp.
Calvin
18

Si vous voulez une compatibilité avec Rails 3 ou Ruby 1.9, essayez le gem date_validator .

Txus
la source
J'ai juste essayé ça. Il a fallu 2 minutes pour installer et ajouter la validation!
jflores
11

L'enregistrement actif vous donne des _before_type_castattributs qui contiennent les données d'attribut brutes avant la conversion de type. Cela peut être utile pour renvoyer des messages d'erreur avec des valeurs pré-typées ou simplement pour effectuer des validations qui ne sont pas possibles après le typage.

J'éviterais la suggestion de Daniel Von Fange de remplacer l'accesseur, car faire la validation dans un accesseur modifie légèrement le contrat de l'accesseur. L'enregistrement actif a une fonctionnalité explicitement pour cette situation. Utilise le.

Joshuacronemeyer
la source
4

Étant donné que vous devez gérer la chaîne de date avant qu'elle ne soit convertie en date dans votre modèle, je remplacerais l'accesseur pour ce champ

Disons que votre champ de date est published_date. Ajoutez ceci à votre objet modèle:

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
la source
Je ne sais pas si je vais l'utiliser à cette fin, mais c'est une excellente suggestion.
Dan Rosenstark
3

Un peu tard ici, mais grâce à " Comment valider une date dans les rails? " J'ai réussi à écrire ce validateur, j'espère qu'il sera utile à quelqu'un:

À l'intérieur de votre 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
la source
1

Voici une réponse non chronique.

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
Voyage
la source
1
Cela renvoie toujours false: "1/1/2013".is_a?(Date)- La date provient de l'entrée de l'utilisateur, elle est donc alimentée sous forme de chaîne. Je devrais d'abord l'analyser comme une date?
Mohamad
Correct. Date.parse("1/1/2013").is_a?(Date), mais vous pouvez voir que s'il n'analyse pas du tout, ce n'est probablement pas non plus une date.
Voyage
1
Besoin de sauver une erreur d'argument ... ahh, ça aurait été génial si ça avait juste analysé la chaîne automatiquement ... eh bien, il y a des gemmes pour ça.
Mohamad
0

Vous pouvez valider la date et l'heure comme ceci (dans une méthode quelque part dans votre contrôleur avec accès à vos paramètres si vous utilisez des sélections personnalisées) ...

# 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
la source
-3

Avez-vous essayé le validates_date_timeplug-in?

Ryan Duffield
la source
Bien que ce lien puisse répondre à la question, il est préférable d'inclure les parties essentielles de la réponse ici et de fournir le lien pour référence. Les réponses aux liens uniquement peuvent devenir invalides si la page liée change.
Pinal
J'aime mieux ma réponse. Deux personnes sont d'accord.
Ryan Duffield
4
@Pinal Le lien est en effet mort maintenant.
Wayne Conrad