It’s a common practice to pass data from a controller to a view using instance variables (@variable). Let’s see what could possibly go wrong when doing this.

We are going to use a simple blog application as an example. In this application we can create posts and add comments to them.

Before

# 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 %>

The biggest problem here is that it’s not so obvious to know where instance variables are set.

  • in an action?
  • in a before_filter?
  • in a helper?
  • in a parent controller? (i.e. ApplicationController)

This implies more difficult debugging and more difficult refactorings.

Also, it’s difficult to reuse views as they are most likely to be tied to multiple instance variables. In our example, what if we wanted our comments partial to be used in something like videos/show.html.erb? We would have to define @post in the show action of our video so that @post.title could return the title of the video. Partials and views are tightly coupled to the controller that sets that instance variable.

After

# 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 %>

We could argue that Rails encourages convention over configuration, so why would we call render ourselves?

When we explicitely render views, we know exactly which file will be rendered and which data is passed to the view.

We are ensured that foo in a view is one of the following:

  • a local view variable (i.e. <% foo = xxx %>)
  • a local variable passed to the view (i.e. locals: { foo: xxx })
  • a method call (i.e. helper method)

As defining local variables inside views is not a good practice, we can limit ourselves to the last two possibilities. It’s now easier to find where these variables come from than when it was when using instance variables.

Using an undefined instance variable would return nil in the view. However, if we forget to pass a local variable inside locals: {}, an exception will be thrown. This allows us to be more confident in our code.