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

Investment Days for productivity

14
.
11
.
2023
Rafał Maliszewski
Visuality
HR

Happy new year

14
.
11
.
2023
Michał Piórkowski
Visuality

Does Norway need Polish software development?

14
.
11
.
2023
Rafał Maliszewski
Ruby on Rails

Visuality is 8 years old

14
.
11
.
2023
Michał Piórkowski
Visuality
Backend

Use less javascript plugins

14
.
11
.
2023
Michał Młoźniak
Frontend

Front-Trends 2015

14
.
11
.
2023
Adam Król
Frontend

Automatic door opener controlled through slack

14
.
11
.
2023
Sakir Temel
Backend
Software
Tutorial

Wolves Summit

14
.
11
.
2023
Michał Piórkowski
Conferences
Business

Berlin Startup Camp

14
.
11
.
2023
Michał Piórkowski
Ruby on Rails
Conferences

Why you shouldn't work at Visuality

14
.
11
.
2023
Michał Piórkowski
Visuality
HR

SaaS Meetup #People

14
.
11
.
2023
Michał Piórkowski
Conferences

Startup Safary Berlin 2015

14
.
11
.
2023
Michał Piórkowski
Conferences

Emmet makes HTML and CSS easier

14
.
11
.
2023
Michał Piórkowski
Frontend
Tutorial

Optional dependencies in gems

14
.
11
.
2023
Karol Słuszniak
Ruby on Rails
Backend

Text messaging with textris gem

14
.
11
.
2023
Karol Słuszniak
Ruby on Rails
Backend