Render Rails views with locals, not instance variables

Written . Tagged Ruby on Rails.

The common style in Rails is to pass data from the controller to the view with instance variables:

app/controllers/my_controller.rb
1
2
3
def show
  @item = Item.find(params[:id])
end
app/views/my_controller/show.html.erb
1
  <p>Item: <%= @item.title %></p>

These instance variables are available in layouts and partials as well, but it’s common to instead pass variables to partials explicitly as locals:

app/views/my_controller/show.html.erb
1
  <%= render "fancy_title", title: @item.title %>
app/views/my_controller/_fancy_title.html.erb
1
  <h1 class="fancy"><%= title %></h1>

With partials, this is definitely a good idea. They’re much easier to reuse and move around when their inputs are explicit.

Lately, our team has been passing them explicitly to regular action views as well. It’s perfectly straightforward:

app/controllers/my_controller.rb
1
2
3
4
5
def show
  render locals: {
    item:  Item.find(params[:id])
  }
end

If you want to type less, here’s a convenient shortcut:

app/controllers/application_controller.rb
1
2
3
4
5
private

def locals(values)
  render locals: values
end
app/controllers/my_controller.rb
1
2
3
def show
  locals item: Item.find(params[:id])
end

Why do this with action views and not just partials?

Instance variables can be set all over the place. From before filters in the current controller. From before filters in a parent controller. If you don’t pass them explicitly to each view, it’s much harder to figure out the values it depends on.

It’s not a lot more to type. We’ve done it for months now and not found it onerous in the least.

Also: your views will confidently explode if they try to use a local variable and you didn’t pass it in; with instance variables, you’d get a nil. Though if you must, even with locals you can get a nil fallback by using local_assigns[:item] instead of item.

If you need to check for the presence of a key, defined?(item) will not work but local_assigns.has_key?(:item) will.

Note that the local assignments of an action view are automatically available in its layout as well. This is perhaps a little on the implicit side, but much less so than instance variables. The locals will not be automatically available in partials, neither inside your action view nor inside the layout.