#17 HABTM Checkboxes
Nella nostra applicazione di esempio, per questo episodio, ci sono due modelli, Product
e Category
, ciascuno con una relazione has_and_belongs_to_many
reciproca. Ciò che vorremmo essere in grado di fare da maschera, è di modificate le categorie in cui è raggruppato un prodotto usando un elenco di checkbox per quel prodotto.
Aggiunta dei checkbox
La form per la modifica del nostro prodotto attualmente ha dei campi per il nome ed il prezzo del prodotto, ma non c’è alcun modo di modificare le categorie in cui è compreso. Ci sono due metodi per aggiungere un checkbox ad una form:
check_box
check_box_tag
check_box_tag
poichè ci darà il controllo di cui abbiamo bisogno sul nome dei checkbox. Il codice che aggiungeremo alla form (products/edit.html.erb
) è mostrato qui sotto.
<% for category in Category.find(:all) %> <div> <%= check_box_tag "product[category_ids][]", category.id, @product.categories.include?(category) %> <%= category.name %> </div> <% end %>
check_box_tag
ha tre parametri: il nome del checkbox, il valore associato al checkbox ed un valore booleano che determina se il checkbox debba essere pre-spuntato. Il nome che abbiamo usato sembra un po’ strano, ma vedrete perchè fra un attimo. Stabiliamo se per una determinata categoria il checkbox vada spuntato o meno, controllando se il prodotto rientra in tale categoria, usando @product.categories.include?(category)
.
Vediamo se funziona
La form di edit con l’aggiunta dei checkbox.
Se aggiorniamo la form e la proviamo, vedremo che le modifiche hanno funzionato e le categorie sono aggiornate quando spuntiamo qualcuno dei checkbox e clicchiamo su ‘submit’. Come fa Rails a sapere come aggiornare il prodotto correttamente? Il log di sviluppo ci da la risposta.
Processing ProductsController#update (for 127.0.0.1 at 2009-01-15 20:57:56) [PUT] Parameters: {"commit"=>"Edit", "authenticity_token"=>"31b711f2c24ae7cea5abf3f758eef46b472eebf3", "product"=>{"price"=>"99.0", "name"=>"Television Stand", "category_ids"=>["2", "4"]}, "id"=>"1"}
Quando viene fatto il submit, la form di edit passa i parametri di categoria nell’hash del prodotto sottoforma di array. Lo fa, perchè abbiamo scelto opportunamente il nome dei checkbox, product[category_ids][]
. Il primo pezzo del nome ("product[category_ids]") dice a Rails di passare category_ids come parte dei parametri in hash del prodotto, mentre le parentesi quadre finali vuote gli dicono di passare i valori all’interno di un array. Ma da dove viene il metodo del modello prodotto category_ids
, che è chiamato quando i parametri per il prodotto sono aggiornati? La risposta è che viene generato in automatico dal metodo has_and_belongs_to_many
dichiarato nella classe di modello Product
. Possiamo provare questa affermazione, aprendo una script/console
e aggiornando il prodotto a mano.
>> p = Product.first => #<Product id: 1, name: "Television Stand", price: 99.0, created_at: "2009-01-11 21:32:12", updated_at: "2009-01-11 21:32:12"> >> p.category_ids => [2, 3] >> p.category_ids = [1,4] => [1, 4] >>
Quando facciamo così, Rails produce il seguente codice SQL per aggiornare le categorie del prodotto:
DELETE FROM "categories_products" WHERE product_id = 1 AND category_id IN (2,3) INSERT INTO "categories_products" ("product_id", "id", "category_id") VALUES (1, 1, 1) INSERT INTO "categories_products" ("product_id", "id", "category_id") VALUES (1, 4, 4)
Un piccolo avvertimento
C’è ancora un piccolo problema con il nostro metodo di aggiornamento. Se togliamo la spunta a tutti i checkbox per rimuovere un prodotto da tutte le categorie, l’aggiornamento non rimuoverà alcuna associazione di categoria per il prodotto. Ciò è dovuto al fatto che un checkbox in una form HTML non manda indietro i propri valori se non è spuntato, per cui no category_ids
apparirà nell’ hash dei parametri del prodotto, ad indicare che i category_ids
non sono aggiornati.
Per sistemare questo problema, dobbiamo cambiare il controller dei prodotti per impostare correttamente il parametro category_ids
in questi casi, in modo tale che sia un array vuoto se non viene passato alla action di update. Possiamo farlo utilizzando l’operatore di Ruby ||=
e aggiungendo la seguente linea in cima all’azione di update:
params[:product][:category_ids] ||= []
Ciò garantisce che se nessun checkbox è spuntato, il prodotto viene aggiornato correttamente in modo tale da essere effettivamente rimosso da tutte le categorie.