#17 HABTM Checkboxes
Dans notre application pour cette épisode, nous avons deux modèles, Product
et Category
, chacun avec une relation has_and_belongs_to_many
avec l'autre. Ce que nous voudrions être capable de faire est de modifier les catégories d'un produit en utilisant une liste de cases à cocher dans le formulaire d'édition de ce produit.
Ajouter les cases à cocher
Le formulaire d'édition de ce produit a actuellement des champs pour le nom du produit et le prix mais il n'y a aucun moyen de modifier les catégories de ce produit. Il y a deux méthodes pour ajouter une case à cocher à un formulaire, check_box
et check_box_tag
. Nous allons utiliser check_box_tag
puisque cette méthode nous donne le contrôle dont nous avons besoin à propos du nom de la case. Le code que nous ajouterons au formulaire est indiqué ci-dessous (products/edit.html.erb
).
<% 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
prend trois paramètres : le nom de la case, la valeur de la case et une valeur booléenne qui détermine si la case doit être cochée. Le nom que nous avons utilisé parait un peu étrange mais vous allez voir pourquoi cette méthode est plus courte. Nous déterminons si chaque case de catégorie doit être cochée ou pas en regardant si le produit est dans cette catégorie en utilisant @product.categories.include?(category)
.
Regardons si ça marche
Le formulaire de modification avec les cases à cocher ajoutées.
Si nos rafraichissons le formulaire et le testons, nous voyons que ça marche et que les catégories du produit sont mises à jour quand nous cochons quelques unes des cases à cocher et cliquons sur ‘submit’. Comment fait Rails pour savoir comment mettre à jour le produit correctement ? Le log de développement à la réponse.
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"}
Quand nous soumettons, le formulaire transmet les paramètres de catégories dans le hash du produit en tant que tableau. Ca se passe comme ça à cause du nom que nous avons donné au cases à cocher, product[category_ids][]
. La première partie du nom dit à Rails de transmettre category_ids comme une partie du hash produit, alors que les accolades vides lui disent de transmettre les valeurs dans un tableau. Mais d'où vient la méthode category_ids
, qui est appelée quand les paramètres du produit sont modifiés ? La réponse est que c'est généré par la méthode has_and_belongs_to_many
dans le modèle Product
. Nous pouvons tester en ouvrant script/console
et en mettant un produit à jour manuellement.
>> 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] >>
Quand nous faisons ça, Rails génère la requête SQL suivante pour mettre à jour les catégories d'un produit :
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)
Une petite chose à corriger
Il y a toujours un problème avec notre méthode de mise à jour. Si nous décochons toutes les cases de catégories d'un produit, la mise à jour échouera. Ceci se produit parce qu'une case à cocher d'un formulaire HTML ne renverra pas ses valeurs si elle est décochée et donc aucun category_ids
n'apparaitra dans le hash des paramètres du produit, ce qui implique que les category_ids
ne sont pas mis à jour.
Pour corriger ceci, nous allons modifier le contrôleur des produits pour définir le paramètre category_ids
comme étant un tableau vide si il n'est pas transmis à l'action de mise à jour. On peut faire ceci en Ruby en utilisant l'opérateur ||=
et ajouter ceci au début de notre action.
params[:product][:category_ids] ||= []
Ceci assurera que si aucune case à cocher n'est sélectionnée, alors le produit est mis à jour correctement et que donc, il n'est dans aucune catégorie.