#32 Time in Text Field
Sopra abbiamo un applicazione che mostra una lista di task. Cliccando sul link di modifica di un task si viene portati alla pagina di edit per quel task. La pagina di edit usa il metodo helper datetime_select
per visualizzare i task come lista di menu a tendina.
Cambiare cinque tendine non è propriamente il modo più semplice per cambiare una data. Sarebbe più semplice per gli utenti se potessero immettere la data e l’ora direttamente in un unico campo di testo. La nostra apllicazione potrebbe poi interpretare ciò che è stato inserito e memorizzare correttamente il dato sul database.
Sostituire le tendine con un campo di testo unico significa che avremo un field in maschera che non mappa direttamente un campo del database. Dobbiamo creare un attributo virtuale nel modello Task
per gestire il nuovo campo. Prima di fare ciò, aggiorniamo il codice della vista e sostituiamo la nostra tendina con un campo di testo chiamato due_at_string
.
<h1>Edit Task</h1> <% form_for @task do |form| %> <ol class="formList"> <li> <%= form.label :name, "Name" %> <%= form.text_field :name %> </li> <li> <%= form.label :due_at_string, "Due at" %> <%= form.text_field :due_at_string %> </li> <li> <%= submit_tag "Edit task" %> </li> </ol> <% end %>
Il codice della vista di modifica con il nostro nuovo campo di testo.
Ora che abbiamo aggiornato la form, dobbiamo creare i metodi getter e setter per l’attributo virtuale nel modello. Gli attributi virtuali sono stati trattati nell’episodio 16; date un’occhiata a quell’episodio se avete bisogno di alcune informazioni in merito.
class Task < ActiveRecord::Base belongs_to :project validates_presence_of :name def due_at_string due_at.to_s(:db) end def due_at_string=(due_at_str) self.due_at = Time.parse(due_at_str) end end
Il metodo getter prende la data di termine del task (due_at
) dal database e restituisce una rappresentazione in formato stringa di questa. Il metodo setter avrebbe potuto essere un po’ più ostico poichè deve accettare qualsiasi cosa abbia deciso di immettere l’utnete e convertirlo in una data e un’ora. Per fortuna, la classe Time
fornisce un metodo di classe parse
che prova a fare proprio questo lavoro di conversione da testo a data.
Ricaridando la pagina vediamo ora che le tendine sono state rimpiazzate dal campo di testo che mostra la data di termine del task. Il metodo Time.parse
è piuttosto ingegnoso nell’interpretare il testo che gli passiamo, per cui potremmo tranquillamente inserire la stringa, per esempio, March 1st at 8:00PM
e questi sarebbe in grado di convertirla.
A volte, tuttavia, abbiamo bisogno di più flessibilità di quanta non ce ne possa fornire il metodo Time.parse
. Il gem Chronic, che può essere installato con sudo gem install chronic
è utile se abbiamo bisogno di poter accettare più tipi di date e orari. Per usarlo, aggiungiamo semplicemente require 'chronic'
in testata alla classe Task
e sostituiamo il Time.parse
all’interno del nostro metodo setter con Chronic.parse
:
def due_at_string=(due_at_str) self.due_at = Chronic.parse(due_at_str) end
Uno dei vantaggi di Chronic è che può gestire le date relative, per cui potremmo passare "tomorrow", "Monday" o persino "next Tuesday at 8pm" e questi sarebbe in grado di interpretarle correttamente.
Gestione delle eccezioni
Chronic restituisce nil
se non è in grado di interpretare la stringa passatagli, che potremmo controllare, ma Time.parse
invece solleva una eccezione ArgumentError
. Ovviamente dobbiamo gestire ciò, per cui aggiungiamo un blocco rescue
al metodo setter. Aggiungiamo anche una variabile di istanza alla classe, che sarà impostata a true
se la data immessa avrà causato una eccezione e infine aggiungiamo un metodo validate
alla classe, che aggiunga un errore all’hash degli errori per il task se il nome passato non può essere interpretato.
def due_at_string=(due_at_str) self.due_at = Time.parse(due_at_str) rescue ArgumentError @due_at_invalid = true end def validate errors.add(:due_at, "is invalid") if @due_at_invalid end
Ora, se proviamo a passare una data non valida, l’errore sarà catturato e aggiunto alla lista degli errori del modello. Time.parse
sostituisce le eventuali parti mancanti o completamente invalide della data immessa con l’equivalente parte di Time.now
, in questo modo se passiamo un valore completamente sbagliato tipo "Hello, world!"
la data di termine del task sarà impostata alla data odierna. Viene sollevata un’eccezione solo se viene passata una data non valida tipo "32-12-2009"
.
Ora conosciamo un modo decisamente migliore per inserire le date e gli orari in una applicazione Rails. Si che scegliamo di usare Chronic o i metodi di interpretazione degli orari built-in, possiamo gestire un ampio ventaglio di formati provenienti da input dell’utente.