Associations polymorphiques
Posté par Nima le 29 avril 2012
- connaître l'héritage ;
- avoir déjà vu les différentes associations (One-to-Many, Many-to-Many, Many-to-Many avancées).
- créer une association polymorphique ;
- utiliser une association polymorphique comme ressource imbriquée ;
- créer une ressource polymorphique de façon générique.
Qu'est-ce que c'est ?
L'association polymorphique est une association qui permet à un modèle d'appartenir à plusieurs autres modèles. Il ne dépend donc pas d'un unique modèle. Par exemple, un modèle Comment peut appartenir à des Articles, mais aussi à des Events. De cette façon, vous n'avez pas besoin de dupliquer des modèles ayant des informations similaires.
Un exemple concret
Illustrons le problème par un exemple. Supposons que nous avons trois modèles : Article, Event et Photo. Le but est de créer un unique modèle Comment pour nos trois types de ressources.
Voilà un schéma représentatif de ce que l'on veut obtenir :
Création du modèle
Comme toujours, des conventions sont à respecter pour que tout se passe comme prévu. Le modèle Comment n'étant plus lié à un modèle spécifique, il faut lui spécifier un champ qui réfèrera à l'ID de la ressource auquel il appartient ainsi que le type de ce dernier. Voilà comment générer le modèle Comment
$ rails g model Comment content:text commentable_id:integer commentable_type:string
Migration
Pour récapituler, il faut stoquer l'ID de l'objet auquel le commentaire appartient et le type de ce dernier. Côté migration, deux façons d'écrire :
# db/migrate/201101249412_create_comments.rb
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :content
t.integer :commentable_id
t.string :commentable_type
t.timestamps
end
end
end
Ou :
# db/migrate/201101249412_create_comments.rb
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
t.string :content
t.integer :commentable, :polymorphic => true
t.timestamps
end
end
end
Associations dans les modèles
Il faut maintenant déclarer dans les modèles concernés les associations de la façon suivante :
# app/models/comment.rb class Comment < ActiveRecord::Base belongs_to :commentable, :polymorphic => true ... # app/models/event.rb class Event < ActiveRecord::Base has_many :comments, :as => :commentable ... # app/models/photo.rb class Photo < ActiveRecord::Base has_many :comments, :as => :commentable ... # app/models/article.rb class Article < ActiveRecord::Base has_many :comments, :as => :commentable ...
Création d'un commentaire
Si on veut maintenant créer un commentaire et l'associer à un article, on peut simplement faire
@article.comments.create(:content => 'Mon contenu')
Aller plus loin
Ressources imbriquées
Si on veut généraliser et que l'on veut accéder par exemple à tous les commentaires d'un article par l'URL /articles/1/comments il faut utiliser les ressources imbriquées et pour cela modifier les routes.
# config/routes.rb resources :model_name do resources :comments end
En faisant ça pour chaque modèle, la route /model/1/comments redirigera alors vers l'action index de CommentsController. Et c'est dans cette méthode qu'il faut, en fonction du type de la ressource, afficher les commentaires.
Récupérer la ressource
Ryan Bates nous propose dans son RailsCast une méthode pour récupérer la ressource concernée.
# app/controllers/comments_controller.rb
private
def find_commentable
params.each do |name, value|
# Regex correspondant à la forme model_id
if name =~ /(.+)_id$/
# $1 correspond au nom du modèle
return $1.classify.constantize.find(value)
end
end
nil # Retourne nil si rien n'a été trouvé
end
La méthode ci-dessus parcours tous les paramètres envoyés par le client et cherche un paramètre se terminant par _id. Si nous étions à l'url /articles/1/comments, nous aurions par exemple article_id avec comme valeur 1.
Si la méthode trouve une correspondance dans les paramètres, elle appelle la méthode classify sur le nom du modèle. La méthode classify transforme les chaînes de caractère s'apparentant à des noms de tables en des chaînes de caractère pouvant correspondre à des noms de classe. Par exemple
"egg_and_hams".classify # => "EggAndHam" "posts".classify # => "Post"
Puis, la méthode constantize est appelé pour essayer de trouver une constante correspondante. «Article» deviendra alors la constante correspondant à la classe Article.
Enfin, la méthode find est appelé pour récupérer l'objet en question.
Afficher et créer un commentaire
De cette façon, on peut maintenant afficher dans notre vue index de comments, tous les commentaires de l'objet concerné.
# app/controllers/comments_controller.rb def index @commentable = find_commentable @comments = @commentable.comments end
Et dans la vue :
# app/views/comments/index.html.erb
<h1>Liste des commentaires</h1>
<ul id="comments">
<% @comments.each do |comment| %>
<li><%= comment.content %></li>
<% end %>
</ul>
<h2>Nouveau commentaire</h2>
<% form_for [@commentable, Comment.new] do |form| %>
<ol class="formList">
<li>
<%= form.label :content %>
<%= form.text_area :content, :rows => 5 %>
</li>
<li><%= submit_tag "Add comment" %></li>
</ol>
<% end %>
Et enfin, pour l'action create du contrôleur
# app/controllers/comments_controller.rb def create @commentable = find_commentable @comment = @commentable.comments.build(params[:comment])
Voilà pour ce qui est des associations polymorphiques !
Références
- Rails Casts de Ryan Bates ;
- ActiveRecord Association RailsGuides.