On The Rails Again

Ressources imbriquées

Posté par Nicolas le 11 mai 2012

Pré-requis :
Ce que nous allons voir :
  • 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

Ressources