Jak sprawdzić datę w listwach?

92

Chcę sprawdzić datę w moim modelu w Ruby on Rails, jednak wartości dnia, miesiąca i roku są już przekonwertowane na niepoprawną datę, zanim dotrą do mojego modelu.

Na przykład, jeśli moim zdaniem wprowadzę 31 lutego 2009 r., Kiedy używam Model.new(params[:model])w kontrolerze, konwertuje to na „3 marca 2009 r.”, Który mój model postrzega jako prawidłową datę, która jest, ale jest niepoprawna.

Chciałbym móc przeprowadzić tę walidację w moim modelu. Czy jest jakiś sposób, w jaki mogę, czy też całkowicie się mylę?

Znalazłem to „ Sprawdzanie daty ”, które omawia problem, ale nigdy nie zostało rozwiązane.

Megamug
źródło
4
To pytanie jest obecnie jednym z najlepszych wyników Google dla sprawdzania poprawności dat w Railsach. Możesz tego przegapić za pierwszym podejściem, tak jak ja: ważną częścią wybranej odpowiedzi jest klejnot validates_timeliness. Nie przeczytałem nawet reszty odpowiedzi, ponieważ dowiedziałem się o validates_timeliness gdzie indziej, a ten klejnot robi wszystko, czego potrzebuję, bez konieczności pisania niestandardowego kodu.
Jason Swett

Odpowiedzi:

61

Domyślam się, że używasz date_selectpomocnika do generowania tagów dla daty. Innym sposobem jest użycie pomocnika wyboru formularza dla pól dnia, miesiąca i roku. W ten sposób (przykład, którego użyłem, to pole 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 %>

W modelu sprawdzasz datę:

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

Jeśli szukasz rozwiązania wtyczki, skorzystałbym z wtyczki validates_timeliness . Działa to tak (ze strony 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 

Lista dostępnych metod walidacji jest następująca:

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
źródło
Sugerowane rozwiązanie nie działa u mnie i używam Railsów 2.3.5
Roger
Przepisałem go, by był bardziej przejrzysty, i przetestowałem go z Railsami 2.3.5 i to działa.
Jack Chu,
Cześć Jack, wypróbowałem twoje rozwiązanie, działa dla mnie, dodając attr_accessible: dzień,: miesiąc,: rok. Co dla mnie nie ma sensu ... Dzięki, jeśli masz jakiś pomysł!
benoitr
W Railsach 3 używam github.com/adzap/validates_timeliness i jest niesamowity.
Jason Swett
Validates_timeliness plugin / gem jest bardzo miły w użyciu. Działa czarująco i sprawia, że ​​mój kod jest mniejszy. Dzięki za wskazówkę.
mjnissim
20

Korzystanie z chronicznego klejnotu:

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
zrozumiałem
źródło
3
W tym konkretnym przykładzie musiałem użyć Chronic.parse(from_date_before_type_cast), aby uzyskać wejściową wartość ciągu. W przeciwnym razie Railsy przerzuciłyby łańcuch na typ, to_dateponieważ pole było datetimepolem.
Calvin,
18

Jeśli chcesz kompatybilność z Rails 3 lub Ruby 1.9, wypróbuj gem date_validator .

Txus
źródło
Właśnie tego spróbowałem. Instalacja i dodanie weryfikacji zajęło 2 minuty!
jflores
11

Moduł Active Record podaje _before_type_castatrybuty, które zawierają surowe dane atrybutów przed rzutowaniem. Może to być przydatne do zwracania komunikatów o błędach z wartościami przed typowaniem lub po prostu przeprowadzania walidacji, które nie są możliwe po rzutowaniu typu.

Unikałbym sugestii Daniela Von Fange, aby zastąpić akcesor, ponieważ wykonanie walidacji w akcesorium nieznacznie zmienia kontrakt akcesora. Moduł Active Record ma funkcję wyraźnie dostosowaną do tej sytuacji. Użyj tego.

joshuacronemeyer
źródło
4

Trochę się spóźniłem, ale dzięki „ Jak sprawdzić datę w railsach? ” Udało mi się napisać ten walidator, nadzieja komuś się przyda:

Wewnątrz twojego 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
źródło
4

Ponieważ musisz obsłużyć ciąg daty, zanim zostanie przekonwertowany na datę w modelu, nadpisałbym akcesor dla tego pola

Powiedzmy, że Twoje pole daty to published_date. Dodaj to do obiektu modelu:

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
źródło
Nie wiem, czy użyję tego do tego celu, ale to świetna sugestia.
Dan Rosenstark
1

Oto niechroniczna odpowiedź.

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
Wyjazd
źródło
1
To zawsze zwraca false: "1/1/2013".is_a?(Date)- Data pochodzi z danych wprowadzonych przez użytkownika, więc jest podawana jako ciąg. Musiałbym najpierw przeanalizować to jako datę?
Mohamad
Poprawny. Date.parse("1/1/2013").is_a?(Date), ale możesz zobaczyć, że jeśli w ogóle nie analizuje, prawdopodobnie również nie jest datą.
Wycieczka
1
Trzeba uratować błąd argumentu ... ach, byłoby wspaniale, gdyby po prostu przeanalizował łańcuch automatycznie ... no cóż, są do tego klejnoty.
Mohamad
0

Możesz zweryfikować datę i godzinę w ten sposób (w metodzie gdzieś w kontrolerze z dostępem do twoich parametrów, jeśli używasz niestandardowych selekcji) ...

# 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
źródło
-2

Czy wypróbowałeś validates_date_timewtyczkę?

Ryan Duffield
źródło
Chociaż ten link może odpowiedzieć na pytanie, lepiej jest zawrzeć tutaj zasadnicze części odpowiedzi i podać link do odniesienia. Odpowiedzi zawierające tylko łącze mogą stać się nieprawidłowe, jeśli połączona strona ulegnie zmianie.
Pinal
Bardziej mi się podoba moja odpowiedź. Dwie osoby się zgadzają.
Ryan Duffield,
4
@Pinal Link rzeczywiście jest teraz martwy.
Wayne Conrad