Passer des données d’un contrôleur à une vue en utilisant une variable d’instance (@variable) est quelque chose de courant. Nous allons voir en quoi cette pratique pourrait nous causer du tort.

Pour illustrer l’exemple, nous allons utiliser un simple blog. Dans cette application on peut créer des articles et y ajouter des commentaires.

Avant

# app/controllers/posts_controller.rb

class PostsController < ApplicationController
  before_action :set_post, only: [:show]

  def show
    @comments = @post.comments
  end

  private

  def set_post
    @post = Post.find(params[:id])
  end
end
<% # app/views/posts/show.html.erb %>

<%= @post.title %>
<%= @post.body %>
<%= render 'posts/comments' %>
<% # app/views/posts/_comments.html.erb %>

Comments on "<%= @post.title %>"
<%= render @comments %>

Le plus gros problème ici est qu’il n’est pas évident de savoir où ces variables d’instance sont définies.

  • Dans une méthode ?
  • Dans un before_filter ?
  • Dans un helper ?
  • Dans un contrôleur parent ? (ex: ApplicationController)

Cela sous-entend des debugs et des refactorings plus difficiles.

Il sera également plus difficile de réutiliser des vues étant donné qu’elles sont liées à plusieurs variables d’instance. Dans notre example, que se passe t-il si on veut utiliser notre partial dans videos/show.html.erb ? On devrait définir @post pour que @post.title retourne le titre de la vidéo.

Les partials et les vues sont fortement couplées au contrôleur qui déclare les variables d’instances.

Après

# app/controllers/posts_controller.rb

class PostsController < ApplicationController
  def show
    post = Post.find(params[:id])
    render_post(post)
  end

  private

  def render_post(post)
    render 'posts/show', locals: { post: post, comments: post.comments }
  end
end
<% # app/views/posts/show.html.erb %>

<%= post.title %>
<%= post.body %>
<%= render partial: 'posts/comments', locals: { comments: comments, title: post.title } %>
<% # app/views/posts/_comments.html.erb %>

Comments on "<%= title %>"
<%= render comments %>

Pourquoi appeler render nous-même alors que Rails encourage la convention plutôt que la configuration ?

Quand on fait appel à render de manière explicite, on sait exactement quel fichier est appelé et quelles données sont passées à la vue.

On peut affirmer que foo dans une vue est soit:

  • une variable locale (ex: <% foo = xxx %>)
  • une variable locale passée à la vue (ex: locals: { foo: xxx })
  • un appel de méthode (par exemple un helper)

Étant donné que définir des variables locales dans les vues n’est pas une bonne pratique, on peut se limiter aux deux derniers points. Il est désormais plus facile de trouver d’où viennent ces variables comparé aux variables d’instance.

Utiliser une variable d’instance non définie dans une vue retournerait nil. En revanche, si on oublie de passer une variable locale dans locals: {}, une exception serait levée. Cela nous permet d’être plus confiant dans notre code.