29
.
04
.
2024
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

LLM Embeddings in Ruby - Paweł Strzałkowski

LLM Embeddings in Ruby

17
.
03
.
2024
Paweł Strzałkowski
Ruby
LLM
Embeddings
ChatGPT
Ollama
Handling Errors in Concurrent Ruby, Michał Łęcicki

Handling Errors in Concurrent Ruby

14
.
11
.
2023
Michał Łęcicki
Ruby
Ruby on Rails
Tutorial
Recap of Friendly.rb 2024 conference

Insights and Inspiration from Friendly.rb: A Ruby Conference Recap

02
.
10
.
2024
Kaja Witek
Conferences
Ruby on Rails

Covering indexes - Postgres Stories

14
.
11
.
2023
Jarosław Kowalewski
Ruby on Rails
Postgresql
Backend
Ula Sołogub - SQL Injection in Ruby on Rails

The Deadly Sins in RoR security - SQL Injection

14
.
11
.
2023
Urszula Sołogub
Backend
Ruby on Rails
Software
Michal - Highlights from Ruby Unconf 2024

Highlights from Ruby Unconf 2024

14
.
11
.
2023
Michał Łęcicki
Conferences
Visuality
Cezary Kłos - Optimizing Cloud Infrastructure by $40 000 Annually

Optimizing Cloud Infrastructure by $40 000 Annually

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

Smooth Concurrent Updates with Hotwire Stimulus

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

Freelancers vs Software house

02
.
10
.
2024
Michał Krochecki
Visuality
Business

Table partitioning in Rails, part 2 - Postgres Stories

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

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