#29 group_by Month
Quasi tutte le applicazioni Rails mostrano una lista di modelli e benchè tipicamente vengano semplicemente mostrati in un certo ordine, può talvolta essere utile anche il raggruppamento di tali record. Sotto c’è un applicazione che mostra una lista di task, ciascuno dei quali ha una data di scadenza. Vogliamo mostrare i task raggruppati per mese di data di scadenza, piuttosto che semplicemente in lista. Per fare ciò useremo un metodo chiamato group_by
.
I nostri task come una semplice, lunga lista.
Per mostrare come funziona il metodo group_by
lo usiamo in console:
>> a = (1..20).to_a => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] >> a.group_by { |num| num/5 } => {0=>[1, 2, 3, 4], 1=>[5, 6, 7, 8, 9], 2=>[10, 11, 12, 13, 14], 3=>[15, 16, 17, 18, 19], 4=>[20]} >>
Raggruppamento di un array con il metodo group_by
.
In questo codice abbiamo creato un array a partire da un range da 1 a 20. Abbiamo poi invocato la group_by
sull’array. group_by
accetta un blocco come argomento e restituisce un hash. Il nostro blocco restituisce il numero passatogli, diviso per due (parte intera).
Ogni chiave dell’hash restituito dal metodo group_by
sarà derivante dal valore di ritorno del blocco, e ogni valore dello stesso hash sarà un array di valori che hanno dato quel determinato valore di ritorno (ora chiave nell’hash) nel blocco. Così, per esempio, i valori 1,2,3 e 4 restituendo tutti 0 se divisi per 5 (divisione fra interi), finiranno tutti in un unico array indicizzato con chiave 0
. Analogamente, 20 è l’unico valore del nostro array che da 4 nella divisione intera per 5, per cui sarà anche l’unico valore all’interno dell’array associato alla chiave 4
.
Una piccola digressione
Il Railscast originale su cui si basa questo episodio era stato scritto ai tempi di Rails 1.x. Da allora, il funzionamento della group_by
ha subito un po’ di modifiche. In Rails 2.0 - 2.2 restituisce un array innestato, per cui, secondo l’esempio precedente, vedremmo il seguente output:
=> [[0, [1, 2, 3, 4]], [1, [5, 6, 7, 8, 9]], [2, [10, 11, 12, 13, 14]], [3, [15, 16, 17, 18, 19]], [4, [20]]]
Nelle ultime versioni di Rails, (Rails 2.3.x), viene restituito un oggetto OrderedHash
.
Torniamo ai task
Ritornando al nostro progetto, usiamo ora la group_by
per raggruppare i nostri task per mese. Lo facciamo, modificando il nostro TaskController
:
class TasksController < ApplicationController def index @tasks = Task.all @task_months = @tasks.group_by { |t| t.due_at.beginning_of_month } end end
Stiamo passando ogni task alla blocco della group_by
e restituiamo una data che è il primo giorno del mese della data di chiusura di quel task. Così facendo genereremo un hash le cui chiavi sono una data che rappresenta il primo giorno di un mese, e i cui valori sono un array di tutti i task la cui data di chiusura rientra in quello stesso mese.
Aggiornamento della vista
<% @tasks.each do |task| %> <p><b><%= task.name %></b> due on <%= task.to_date.to_s(:long) %></p> <% end %>
Il codice originale della vista itera fra i task e li mostra. Cambieremo questo codice in modo tale che iteri fra ogni chiave del nostro hash di task raggruppati e poi attraverso ogni task presente fra i valori di quella chiave. Il nuovo codice della vista sarà così:
<% @task_months.each do |month, tasks| %> <h2><%= month.strftime("%B %Y") %></h2> <% tasks.each do |task| %> <p><b><%= task.name %></b> due at <%= task.due_at.to_date.to_s(:long) %></p> <% end %> <% end %>
Il codice aggiornato della vista.
Il codice ora itera fra le chiavi nell’ hash (ciascuna delle quali è una data che rappresenta il primo giorno di un determinato mese) e stampa il mese e l’anno di quella data usando strftime
. Poi si itera attraverso ogni task di quel mese e se ne stampano i suoi dettagli. Nel browser possiamo vedere che i task sono ora raggruppati per mese.
I task sono raggruppati correttamente, ma non necessariamente nell’ordine corretto.
C’è ancora un problema aperto, tuttavia. L’hash non ordina le proprie chiavinel modo in cui vorremmo, il che significa, nell’esempio precedente, che c’è un task per gennaio dopo tutti quelli per febbraio. Abbiamo bisogno di ordinare le chiavi in un hash in modo tale che poi vengano processate nell’ordine corretto. La soluzione a questo problema consiste nell’ordinare le chiavi, e poi iterare su queste, prendendo i task per chiave.
<% @task_months.keys.sort.each do |month| %> <h2><%= month.strftime("%B %Y") %></h2> <% for task in @task_months[month] %> <p><b><%= task.name %></b> due at <%= task.due_at.to_date.to_s(:long) %></p> <% end %> <% end %>
Ora i nostri task vengono mostrati nell’ordine corretto.
<<<<<<< HEAD ======= >>>>>>> b97aa6197477c8dd0eb373f63b133400a451886fNon occorre sottolineare che la group_by
può essere usata per raggruppare i nostri task in base ad altre proprietà. Per esempio se volessimo, potremmo raggrupparli in base alla prima lettera del nome in maniera altrettanto semplice. Questo rende il group_by
un potente sistema di raggruppamento liste di elementi.