2010-07-21 14 views
9

Ho un campo data in un modulo tipo sostenuto nel mio Rails app:Rails date_select aiutante e validazione

<%= 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'} 
%> 

E 'in corso di validazione nel codice modello utilizzando:

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

Purtroppo, se l'utente inserisce un valore parziale come solo l'anno, il modulo viene comunque inviato senza errori. Invece un valore di data funky viene scritto nel db. Come faccio a rendere obbligatori tutti e tre i campi?

Mi piacerebbe scrivere una convalida personalizzata per questo, ma non so come accedere correttamente ai singoli elementi dell'elemento di compleanno. Come posso fare questo?

Grazie! Moe

+0

Ho notato che non hai ': include_blank => true'. È possibile lasciare uno spazio in bianco? Che data prendi se hai impostato l'anno al 2000? Inoltre,: start_year deve essere precedente a: end_year. – mckeed

+1

Hi mckeed, ': prompt => {: month => 'Month',: day => 'Day',: year => 'Year'}' inserisce valori predefiniti vuoti per mese, giorno e anno. ': end_year' è minore di': start_year' perché volevo che i valori ordinati desc. – Moe

risposta

0

Si potrebbe eseguire l'override del metodo validate_on_create, come il seguente:

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

Date.parse non funzionerà qui poiché il valore della data non è valido. è solo funky. se lasci l'anno vuoto, riceverai qualcosa come "0007-01-01". – Moe

+0

Hai ragione. Invece di analizzare, dovresti usare civil. In questo modo: Date.civil (compleanno [: anno] .to_i, compleanno [: mese] .to_i, compleanno [giorno] .to_i) Questo dovrebbe generare un ArgumentError se qualsiasi campo è vuoto. – sespindola

+0

Sfortunatamente, questo non ha funzionato neanche. Genera un errore anche se inserisco correttamente tutti i campi. Ho tentato di scrivere un metodo di convalida personalizzato nella classe del modello, ma non posso accedere al valore del campo utilizzando 'compleanno [: mese]' o anche con l'estensione '.to_i'. Qualche idea su come fare questo? – Moe

2

penso che avrebbe dovuto creare la convalida del controller stesso.

Le parti della data vengono passate a birthday(1i), birthday(2i) e birthday(3i). Il problema qui è che vengono assegnati immediatamente al passaggio degli attributi e quindi prima che avvengano le convalide.

È anche possibile sovrascrivere il metodo attributes= per creare la propria convalida, ma non suggerirei di farlo.

Ricordare che se si eseguono le convalide, potrebbe essere opportuno verificare anche eventuali date errate. (per esempio il 31 febbraio, che una volta superato darà il 2 marzo e non un errore).

Penso che il problema principale è che ActiveRecord è in realtà sostituendo i valori vuoti con 1 prima di creare il Date, il che significa anche che se il visitatore passa solo l'anno, verrà creata la data il 1 ° gennaio dello stesso anno . Immagino che sia un comportamento atteso per consentire l'uso di solo una selezione di anno/mese/giorno e creare comunque una data utile.

+0

Grazie per le informazioni, in realtà questo è un rito del bug ?. come risolvere questo problema? –

1

Relativo a questo post, questa è la soluzione migliore che ho trovato. Tuttavia devo aggiungere: giorno,: mese: anno come attr_accessible, cosa che non capisco perchè .. (? A causa di convalida per favore fatemi sapere ..)

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 

utente modulo di registrazione

<%= 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

Il motivo per cui lo aggiungete a "attr_accessible' è quello di consentire che gli attributi vengano assegnati in base alla massa. Altrimenti verrebbero estrapolati quando si esegue 'User.new (params [: user])' o '@ user.update_attributes (params [: user])'. Tuttavia, pensavo che non fosse necessario a meno che non lo avessi già specificato (cioè, quando non è specificato affatto, tutti gli attributi nell'hash vengono inviati al modello, ma forse non si applicano agli attributi virtuali) –

0

Dopo aver seguito i suggerimenti di Benoitr mi si avvicinò con qualcosa di simile utilizzando attributi virtuali. Sul lato Vista ci sono 3 selezioni separate (anno, lunedì, giorno) all'interno di un 'fields_for'. I dati vengono inviati all'assegnazione di massa del controllore (nessuna modifica nel controller, vedere asciicasts #16) e quindi passati a un getter/setter (cioè l'attributo virtuale) nel modello. Sto usando Rails 3.0.3 e simpleForm per il codice di visualizzazione.

Nella Vista:

<%= 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 %> 

Nel Modello:

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' è l'attributo virtuale che imposta il valore di design_date nel database. Il getter restituisce un hash simile a quello che viene inviato nel modulo. Il setter controlla gli spazi vuoti e crea un nuovo oggetto data, lo imposta e imposta anche la variabile di errore. Il validatore personalizzato: design_date_validator controlla la variabile dell'istanza dell'errore e imposta la variabile errori. Ho usato ': base' perché il nome della variabile non era leggibile dall'uomo e l'uso di base rimuove quel valore dalla stringa di errore.

Alcune cose da rifattorizzare potrebbero essere l'errore che controlla la variabile di istanza, ma sembra funzionare almeno. Se qualcuno conosce un modo migliore per aggiornare gli oggetti Date mi piacerebbe sentirlo.

Problemi correlati