2010-07-21 17 views
9

ich ein Datumsfeld in einem Modell gesichert Form in meiner Rails App haben:Rails date_select Helfer und Validierung

<%= f.date_select :birthday, 
    {:start_year => Time.now.year, 
    :end_year => 1900, 
    :use_short_month => true, 
    :order => [:month, :day, :year], 
    :prompt => {:month => 'Month', :day => 'Day', :year => 'Year'}}, 
    {:class => 'year', 
    :id => 'user_birthday'} 
%> 

Es ist im Modellcode validiert wird unter Verwendung von:

validates_presence_of :birthday, :message => 'is a required field' 

Leider, wenn Der Benutzer gibt einen Teilwert ein, z. B. nur das Jahr, das Formular wird dennoch ohne Fehler übergeben. Stattdessen wird ein funky Datum Wert in die db geschrieben. Wie mache ich alle drei Felder obligatorisch?

Ich möchte eine benutzerdefinierte Validierung dafür schreiben, aber ich weiß nicht, wie Sie die einzelnen Teile des Geburtstagselements richtig zugreifen. Wie kann ich das machen?

Danke! Moe

+0

Ich bemerke, dass Sie nicht ': include_blank => true' haben. Ist es überhaupt möglich, einen Rohling zu lassen? Welches Datum bekommen Sie, wenn Sie das Jahr auf 2000 setzen? Außerdem: start_year sollte früher als: end_year sein. – mckeed

+1

Hi mckeed, ': prompt => {: Monat => 'Monat',: Tag => 'Tag',: Jahr => 'Jahr'}' fügt leere Standardwerte für Monat, Tag und Jahr ein. ': end_year' ist kleiner als': start_year', weil ich die bestellten Werte desc wollte. – Moe

Antwort

0

könnte Sie überschreiben die validate_on_create Methode, wie folgt aus:

class MyModel < ActiveRecord::Base 
    def validate_on_create 
    Date.parse(birthday) 
    rescue 
    errors.add_to_base("Wrong date format") 
    end 
end 
+0

Date.parse wird hier nicht funktionieren, da der Datumswert nicht ungültig ist. es ist einfach irre. Wenn Sie das Jahr leer lassen, erhalten Sie etwas wie "0007-01-01". – Moe

+0

Sie haben Recht. Anstatt zu analysieren, sollten Sie zivile verwenden. So: Date.civil (Geburtstag [: Jahr] .to_i, Geburtstag [: Monat] .to_i, Geburtstag [Tag] .to_i) Das sollte einen ArgumentError werfen, wenn ein Feld leer ist. – sespindola

+0

Leider hat das auch nicht funktioniert. Es wirft einen Fehler auf, selbst wenn ich alle Felder richtig eingebe. Ich habe versucht, eine benutzerdefinierte Validierungsmethode in der Modellklasse zu schreiben, aber ich kann nicht auf den Feldwert zugreifen, indem ich 'birthday [: month]' oder sogar mit '.to_i' angehängt habe. Irgendwelche Ideen, wie man das macht? – Moe

2

Ich glaube, Sie würden die Validierung im Controller selbst erstellen haben.

Die Datumsteile werden an birthday(1i), birthday(2i) und birthday(3i) übergeben. Das Problem hierbei ist, dass sie beim Übergeben der Attribute und somit vor dem Auftreten von Validierungen sofort zugewiesen werden.

Sie können auch die attributes= Methode überschreiben, um Ihre eigene Validierung dort zu erstellen, aber ich würde nicht vorschlagen, dass Sie das tun.

Denken Sie daran, dass es bei Validierungen sinnvoll ist, auch bei einem falschen Datum zu validieren. (zum Beispiel der 31. Februar, der, wenn er bestanden wird, den 2. März ergibt und kein Fehler). hier

ich glaube, das Hauptproblem ist, dass Active tatsächlich die leeren Werte mit 1 ersetzen vor den Date zu schaffen, was auch bedeutet, dass, wenn der Besucher nur das Jahr passiert, wird das Datum in diesem Jahr auf dem 1. Januar erstellt werden . Ich denke, das ist ein erwartetes Verhalten, um nur eines von Jahr/Monat/Tag auswählen zu lassen und trotzdem ein nützliches Datum zu erstellen.

+0

Danke für die Info, eigentlich ist das Bugritus ?. Wie behebe ich dieses Problem? –

1

In Bezug auf diese post ist dies die beste Lösung, die ich gefunden habe. Allerdings soll ich hinzufügen: Tag: Monat: Jahr als attr_accessible, was ich verstehe nicht, warum .. (? Weil die Validierung lass es mich wissen ..)

User.rb

MONTHS = ["January", 1], ["February", 2], ... 
DAYS = ["01", 1], ["02", 2], ["03", 3], ... 
START_YEAR = Time.now.year - 100 
END_YEAR = Time.now.year 
YEAR_RANGE = START_YEAR..END_YEAR 

attr_accessible :day, :month, :year 
attr_accessor :day, :month, :year 

before_save :prepare_birthday 

validate :validate_birthday 

private 

def prepare_birthday 
    begin 
    unless year.blank? # in order to avoid Year like 0000 
     self.birthday = Date.new(self.year.to_i, self.month.to_i, self.day.to_i) 
    end 
     rescue ArgumentError 
    false 
    end 
end 

def validate_birthday 
    errors.add(:birthday, "Birthday is invalid") unless prepare_birthday 
end 

Registrierungsformular

<%= f.select :month, options_for_select(User::MONTHS), :include_blank => "Month" %> 
<%= f.select :day, options_for_select(User::DAYS), :include_blank => "Day" %> 
<%= f.select :year, options_for_select(User::YEAR_RANGE), :include_blank =>"Year" %> 
+1

Der Grund, warum Sie es zu attr_accessible hinzufügen, besteht darin, den Attributen eine Massenzuweisung zu gewähren. Andernfalls würden sie entfernt werden, wenn Sie 'User.new (params [: user])' oder '@ user.update_attributes (params [: user])' ausführen. Allerdings dachte ich, dass das nicht notwendig war, es sei denn, Sie hatten es bereits angegeben (wenn es überhaupt nicht angegeben wird, werden alle Attribute im Hash an das Modell gesendet, aber vielleicht nicht für virtuelle Attribute) –

0

Nach Benoitr Vorschläge folgenden kam ich mit etwas ähnliches mit virtuellen Attribute auf. Auf der View-Seite gibt es 3 separate Auswahlmöglichkeiten (Jahr, Monat, Tag) innerhalb eines 'fields_for'. Die Daten werden der Massenzuweisung des Controllers übergeben (keine Änderungen im Controller, siehe asciicasts #16) und dann an ein Getter/Setter (d. H. Virtuelles Attribut) im Modell übergeben. Ich verwende Rails 3.0.3 und simpleForm für den View-Code.

Im Ausblick:

<%= f.label "Design Date", :class=>"float_left" %> 
<%= f.input :design_month, :label => false, :collection => 1..12 %> 
<%= f.input :design_day, :label => false, :collection => 1..31 %> 
<%= f.input :design_year, :label => false, :collection => 1900..2020 %> 

Im Modell:

validate :design_date_validator 

def design_year 
    design_date.year 
end 
def design_month 
    design_date.month 
end 
def design_day 
    design_date.day 
end 
def design_year=(year) 
    if year.to_s.blank? 
    @design_date_errors = true 
    else  
    self.design_date = Date.new(year.to_i,design_date.month,design_date.day) 
    end 
end 
def design_month=(month) 
    if month.to_s.blank? 
    @design_date_errors = true 
    else  
    self.design_date = Date.new(design_date.year,month.to_i,design_date.day) 
    end 
end 
def design_day=(day) 
    if day.to_s.blank? 
    @design_date_errors = true 
    else  
    self.design_date = Date.new(design_date.year,design_date.month,day.to_i) 
    end 
end 
#validator 
def design_date_validator 
    if @design_date_errors 
    errors.add(:base, "Design Date Is invalid") 
    end 
end 

'design_date_attr' ist das virtuelle Attribut, das den Wert von design_date in der Datenbank festlegt. Der Getter übergibt einen Hash, der dem ähnelt, was im Formular übermittelt wird. Der Setter prüft auf Leerzeichen und erstellt ein neues Datumsobjekt und setzt es sowie die Fehlervariable. Der benutzerdefinierte Validator: design_date_validator sucht nach der Fehlerinstanzvariablen und legt die Fehlervariable fest. Ich habe ': base' verwendet, weil der Variablenname nicht vom Menschen lesbar war und die Verwendung von base diesen Wert aus der Fehlerzeichenfolge entfernt.

Ein paar Dinge zu refactor könnte die Instanzvariable Fehlerprüfung sein, aber es scheint zumindest zu funktionieren. Wenn jemand eine bessere Möglichkeit zum Aktualisieren der Date-Objekte kennt, würde ich es gerne hören.