14
.
11
.
2023
11
.
08
.
2022
Ruby on Rails
Frontend
Backend
Tutorial

Should I use instance variables in Rails views?

Mateusz Woźniczka
Ruby Developer

Should I use instance variables in Rails views?

In Ruby, an instance variable has a name starting with the @ character and is available only within the object's scope. In other words, it is accessible by all object's methods, without the need to pass it explicitly, but it is not shared between the objects.

Instance variables in Rails

Rails extends this idea and makes it look like instance variables are 'available' between some objects.

In the code below there are three instance variables initialized in the controller: @post, @comments, and @author, that are called in the show view template, and two partials rendered in that template.

# controllers/post_controller.rb
class PostsController < ApplicationController

  def show
    @post = Post.find(params[:id])
    @author = 'John Doe'
    @comments = @post.comments
  end
end
<!-- views/posts/show.html.erb -->
<%= render "shared/author" %>

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>

<p>
  <strong>Body:</strong>
  <%= @post.body %>
</p>

<%= render "shared/comments" %>
<!-- views/shared/author.html.erb -->
<h4>Written by: <%= @author %></h4>
<!-- views/shared/comments.html.erb -->
<h3>Comments</h3>

<% @comments.each do |comment| %>
 <p>
   <%= comment.body %>
 </p>
<% end %>

Controller’s instance variables are copied to the view initializer, so they are available as the Action View's object's own instance variables. When the view is rendered, the instance variables used in the template are resolved and we are getting an HTML with all the data we wanted to present in it.

The view is not accessing controllers instance variables - it is using its own. That means, that no Ruby rules are bent or broken, and there are simply two objects sharing an access to the same objects via their instance variables.

Making instance variables accessible inside the whole view templates (including partials) is making the development process easier (because you do not have to worry about passing them around), but also can lead to some troubles and confusion in further development.

Potential issues

Global access

As you can see in the code above, an instance variable can be accessed anywhere in the view - @author is called inside the _author.html.erb partial, even though it is not used in the show.html.erb.

It is also possible to create such a variable in the partial (or any other part of the view) or even modify the existing one, which is obviously not a good practice. The following code is valid from a semantic point of view but can cause quite a lot of confusion. It is changing the value of @post_title, so in the show page instead of John Doe we will get Foo.

<!-- views/shared/author.html.erb -->
<h4>Written by: <%= @author %></h4>
<% @post.title = 'Foo' %>

Instance variables in views behave just like the global variables - they can be accessed and modified in multiple places, so it is easy to lose a track of what is going on in the view when the project grows.

Unusable partials

Let's assume, that we want to add a new feature that will be displaying the comment's author. We already have the partial with author’s details, so we can use it.

<!-- views/shared/comments.html.erb -->
<h3>Comments</h3>

<% @comments.each do |comment| %>
 <p>
   <%= comment.body %>
 </p>
 <%= render "shared/author" %>
<% end %>

But using it in the current form will not get us the result we want, because it will render details of the article's author.

Fixing partials

To use partials in multiple places, we should use local variables, that are passed to the partial explicitly. Our example will look like this:

<!-- views/posts/show.html.erb -->
<%= render "shared/author", locals: {author: @author} %>

<p>
  <strong>Title:</strong>
  <%= @post.title %>
</p>

<p>
  <strong>Body:</strong>
  <%= @post.body %>
</p>

<%= render "shared/comments", locals: {comments: @comments} %>
<!-- views/shared/author.html.erb -->
<h4>Written by: <%= author %></h4>
<!-- views/shared/comments.html.erb -->
<h3>Comments</h3>

<% comments.each do |comment| %>
 <p>
   <%= comment.body %>
   <%= render "shared/author", locals: {author: comment.author} %>
 </p>
<% end %>

Now the same partial is rendered in multiple places: on the top of the page - where it displays the name of the article's author, and after each comment displaying that comment's author.

This approach resolves also the issue with the global scope of instance variables because now they are still available everywhere, but they are used only on the show view page.

Taking it further

Some developers apply this approach not only to partials, but also to the whole views. In such a case controllers use explicit render method with local variables, so our example would look like this:

# controllers/post_controller.rb
class PostsController < ApplicationController

  def show
    @post = Post.find(params[:id])
    @author = 'John Doe'
    @comments = @post.comments

    render :show, locals: { post: @post, author: @author, comments: @comments }
  end
end
<!-- views/posts/show.html.erb -->
<%= render "shared/author", locals: {author: author} %>

<p>
  <strong>Title:</strong>
  <%= post.title %>
</p>

<p>
  <strong>Body:</strong>
  <%= post.body %>
</p>

<%= render "shared/comments", locals: {comments: comments} %>
<!-- views/shared/author.html.erb -->
<h4>Written by: <%= author %></h4>
<!-- views/shared/comments.html.erb -->
<h3>Comments</h3>

<% comments.each do |comment| %>
 <p>
   <%= comment.body %>
   <%= render "shared/author", locals: {author: comment.author} %>
 </p>
<% end %>

As we already know all instance variables from the controller are passed to the view. Sometimes it may be hard to track all of them because they can be initialized not only in the controller action itself but also in multiple other places like filters, controller methods or even helpers. Using local variables in such cases may help to understand what is passed to the view, especially when the application grows.

Wrap up

Instance variables inside view templates behave like global variables. Using local variables for partials rendering solves most of the issues caused by it, and makes partials reusable. Some developers prefer not to use instance variables at all, and render views with locals from controllers.

Mateusz Woźniczka
Ruby Developer

Check my Twitter

Check my Linkedin

Did you like it? 

Sign up To VIsuality newsletter

READ ALSO

N+1 in Ruby on Rails

14
.
11
.
2023
Katarzyna Melon-Markowska
Ruby on Rails
Ruby
Backend

Turbo Streams and current user

29
.
11
.
2023
Mateusz Bilski
Hotwire
Ruby on Rails
Backend
Frontend

Showing progress of background jobs with Turbo

14
.
11
.
2023
Michał Łęcicki
Ruby on Rails
Ruby
Hotwire
Frontend
Backend

Table partitioning in Rails, part 1 - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Backend
Ruby on Rails

Table partitioning types - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Backend

Indexing partitioned table - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Backend
Postgresql
SQL Views in Ruby on Rails

SQL views in Ruby on Rails

14
.
11
.
2023
Jan Grela
Backend
Ruby
Ruby on Rails
Postgresql
Design your bathroom in React

Design your bathroom in React

14
.
11
.
2023
Bartosz Bazański
Frontend
React
Lazy Attributes in Ruby - Krzysztof Wawer

Lazy attributes in Ruby

14
.
11
.
2023
Krzysztof Wawer
Ruby
Software

Exporting CSV files using COPY - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Postgresql
Ruby
Ruby on Rails
Michał Łęcicki - From Celluloid to Concurrent Ruby

From Celluloid to Concurrent Ruby: Practical Examples Of Multithreading Calls

14
.
11
.
2023
Michał Łęcicki
Backend
Ruby
Ruby on Rails
Software

Super Slide Me - Game Written in React

14
.
11
.
2023
Antoni Smoliński
Frontend
React
Jarek Kowalewski - ILIKE vs LIKE/LOWER - Postgres Stories

ILIKE vs LIKE/LOWER - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Ruby
Ruby on Rails
Postgresql

A look back at Friendly.rb 2023

14
.
11
.
2023
Cezary Kłos
Conferences
Ruby

Debugging Rails - Ruby Junior Chronicles

14
.
11
.
2023
Piotr Witek
Ruby on Rails
Backend
Tutorial

GraphQL in Ruby on Rails: How to Extend Connections

14
.
11
.
2023
Cezary Kłos
Ruby on Rails
GraphQL
Backend
Tutorial

Tetris on Rails

17
.
03
.
2024
Paweł Strzałkowski
Ruby on Rails
Backend
Frontend
Hotwire

EURUKO 2023 - here's what you've missed

14
.
11
.
2023
Michał Łęcicki
Ruby
Conferences

Easy introduction to Connection Pool in ruby

14
.
11
.
2023
Michał Łęcicki
Ruby on Rails
Backend
Ruby
Tutorial

When crazy ideas bring great time or how we organized our first Conference!

04
.
12
.
2023
Alexander Repnikov
Ruby on Rails
Conferences
Visuality

Stacey Matrix & Takeaways - why does your IT project suck?

14
.
11
.
2023
Wiktor De Witte
Project Management
Business

A simple guide to pessimistic locking in Rails

14
.
11
.
2023
Michał Łęcicki
Ruby on Rails
Backend
Ruby
Tutorial

Poltrax design - story of POLTRAX (part 3)

04
.
12
.
2023
Mateusz Wodyk
Startups
Business
Design

Writing Chrome Extensions Is (probably) Easier Than You Think

14
.
11
.
2023
Antoni Smoliński
Tutorial
Frontend
Backend