Ressources imbriquées
Posté par Nicolas le 11 mai 2012
- Avoir compris le fonctionnement des routes.
- Avoir vu le routage de ressources.
- Déclarer les ressources imbriquées au niveau des routes.
- Adapter les contrôleurs et les vues pour gérer ces ressources imbriquées.
Nous avons vu comment définir des relations d'associations entre différents modèles, ainsi que la façon de définir des routes pour des ressources. Passons maintenant à la gestion des ressources imbriquées.
Présentation du problème
Reprenons l'exemple de l'article sur les associations many-to-many pour illustrer le problème. Nous avons un modèle Article et un modèle Tag. Un article peut avoir plusieurs tags et un tag appartient à plusieurs articles. Ces modèles sont les ressources que nous allons manipuler et sont donc liés par une relation many-to-many.
Le problème est que nous voulons représenter cette relation dans les URLs, c'est-à-dire par exemple, accéder à la liste de tous les tags d'un article par l'url : http://monsite.fr/articles/id_de_l_article/tags.
Routes
Afin de représenter cette imbrication de ressources au niveau des URLs, on définit les routes comme ceci :
# config/routes.rb resources :articles do resources :tags end
Cette déclaration permet de définir les routes pour les Articles mais aussi pour les Tags.
CRUD, méthodes HTTP et actions
Voyons de plus près l'ensemble des routes générées :
| Méthode HTTP |
URL | Action | Utilité |
|---|---|---|---|
| GET | /articles | index | Afficher la liste de tous les articles |
| GET | /articles/new | new | Afficher un formulaire pour créer un article |
| POST | /articles | create | Créer un nouvel article |
| GET | /articles/:id | show | Afficher un article |
| GET | /articles/:id/edit | edit | Afficher un formulaire pour éditer un article |
| PUT | /articles/:id | update | Mettre à jour un article |
| DELETE | /articles/:id | destroy | Supprimer un article |
| GET | /articles/:article_id/tags | index | Afficher la liste de tous les tags d'un article |
| GET | /articles/:article_id/tags/new | new | Afficher un formulaire pour créer un tag appartenant à un article |
| POST | /articles/:article_id/tags | create | Créer un nouveau tag pour un article |
| GET | /articles/:article_id/tags/:id | show | Afficher un tag spécifique appartenant à un article |
| GET | /articles/:article_id/tags/:id/edit | edit | Afficher un formulaire pour editer un tag appartenant à un article |
| PUT | /articles/:article_id/tags/:id | update | Mettre à jour un tag appartenant à un article |
| DELETE | /articles/:article_id/tags/:id | destroy | Supprimer un tag appartenant à un article |
Il est important de noter que les URLs permettant d'accéder aux tags requièrent l'id d'un article, en effet, nous allons devoir fournir cet id à chaque path helper. Voyons justement les chemins et URLs qui sont générés à partir de cette déclaration de route.
Chemins et URLs générés
| Path helper | Méthode HTTP |
Action |
|---|---|---|
| articles_path | GET | index |
| POST | create | |
| article_path(:id) Ce path prend en paramètre l'id de l'article à afficher, modifier ou supprimer | GET | show |
| PUT | update | |
| DELETE | destroy | |
| new_article_path | GET | new |
| edit_article_path(:id) Ce path prend en paramètre l'id de l'article à modifier | GET | edit |
| article_tags_path | GET | index |
| POST | create | |
| article_tag_path(:article_id, :tag_id) Ce path prend en paramètre l'id de l'article auquel appartient le tag à modifier ainsi que l'id de ce dernier | GET | show |
| PUT | update | |
| DELETE | destroy | |
| new_article_tag_path | GET | new |
| edit_article_tag_path(:article_id, :tag_id) Ce path prend en paramètre l'id de l'article auquel appartient le tag à modifier ainsi que l'id de ce dernier | GET | edit |
Contrôleur
Après avoir modifié les routes, il est souvent nécessaire d'apporter des modifications au code existant pour pouvoir faire fonctionner l'application avec les ressources imbriquées. Par exemple, si vous avez générés vos modèles à l'aide de scaffold et que l'ensemble des vues et contrôleurs existent déjà il faut les modifier.
Ajout d'une référence
Tout d'abord, nos tags sont dépendants d'un article, il faut donc définir cet article dans le contrôleur. Il est possible pour cela de créer une variable faisant référence à l'article auquel appartient le tag en ajoutant ce morceau de code au début de chaque méthode où la référence est nécessaire :
@article = Article.find(params[:article_id])
Le params[:article_id] permet de récupérer l'id de l'article qui est passé dans l'URL.
Pour éviter la duplication de cette ligne de code, on peut créer une méthode qui définie cette variable. Pour que cette méthode soit exécutée avant les autres, on définit un before_filter, comme ceci :
# app/controllers/tags_controller.rb
class TagsController < ApplicationController
before_filter :find_article
def index
...
end
...
def destroy
...
end
private
def find_article
@article = Article.find(params[:article_id])
end
end
Pour en savoir plus sur les filtres en Ruby On Rails
Modification des path helpers
Maintenant que la référence à l'article auquel appartient le tag est définie, il faut modifier les redirect_to dans les méthodes create, update et destroy comme ceci :
# app/controllers/tags_controller.rb
class TagsController < ApplicationController
…
def create
…
format.html { redirect_to [@article, @tag] }
…
end
def update
…
format.html { redirect_to [@article, @tag] }
…
end
def destroy
…
format.html { redirect_to article_tags_url }
…
end
end
Il faut noter qu'en Ruby On Rails [@article, @tag] est équivalent à article_tag_path(@article, @tag)
Vues
Ensuite il est nécessaire d'adapter l'ensemble des vues précédemment générées avec les scaffolds pour que ça fonctionne. Il faut modifier l'ensemble des paths existant en rajoutant les variables @article et @tag comme par exemple pour la vue index de tags :
<h1>Listing tags</h1>
<table>
<tr>
<th>Name</th>
<th>Article</th>
<th></th>
<th></th>
<th></th>
</tr>
<% @tags.each do |tag| %>
<tr>
<td><%= tag.name %></td>
<td><%= tag.article_id %></td>
<td><%= link_to 'Show', article_tag_path(@article, tag) %></td>
<td><%= link_to 'Edit', edit_article_tag_path(@article, tag) %></td>
<td><%= link_to 'Destroy', article_tag_path(@article, tag), :confirm => 'Are you sure?', :method => :delete %></td>
</tr>
<% end %>
</table>
<br />
<%= link_to 'New Tag', new_article_tag_path %>
Évitez d'imbriquer trop de ressources. Essayez de vous limiter à une imbrication, cela permet d'avoir des URLs plus lisibles et des path helper moins long et moins contraignant à écrire.
Par exemple si vous avez trois ressources Post, Comment et Favorite, vous pouvez diviser l'imbrication :
posts/:post_id/comments/:comment_id/favorites
en deux imbrications :
posts/:post_id/comments/:comment_id
comments/:comment_id/favorites/:favorite_id
Pour cela il faut écrire les routes en deux parties comme ceci :
# config/routes.rb resources :posts do resources :comments end resources :comments do resources :favorites end