If you enjoy this article you may be interested in the book I am working on called Building and Deploying Crypto Trading Bots.
Throughout this post, we will investigate one source of that "Rails magic" that perplexes developers: how Rails assigns Controller instance variables to view templates.
Take the following for example:
# If you inspect action_controller.rb you will find it inerhits from ActionController::Base
class MyController < ApplicationController
def index
@my_index_var = [1,2,3,4]
end
end
<% @my_index_var.each do |i| %>
<p> Number: <%= I %> </p>
<% end %>
Have you ever wondered how on earth the view template index.html.erb
gets access to the instance variable @my_index_var
? Well, let's find out.
What is Rails Anyway?
Without jumping too far into Rails source code (yet), recall that the Ruby on Rails source code isn't a massive monolithic code base. Rather, it is a collection of isolated Ruby gems that are strung together to make up the tool we call Rails. Inside the codebase, the responsibilities of template rendering, rack request routing, object relational mapping and more are divided across several key gems that are usually prefixed with Action*
or Active*
. All these gems work together to deliver the end framework you and I use for web application development. With the primer out of the way, let's begin the journey to expose how controller instance variables are assigned to views.
ActionPack'd
Our first stop is the gem called ActionPack
. ActionPack
defines several important modules including ActionController
, ActionDispatch
and AbstractController
. Thematically the gem revolves around framework controller code, rendering and routing of rack requests.
For our investigation, the first class in ActionPack
to take a look at is ActionController::Base
. This is the base class that all your application controllers will inherit from.
class ApplicationController < ActionController::Base
end
class Posts < ApplicationController
end
In essence ActionController::Base
is the core of a web request in Rails. It gives controllers the ability to define actions for requests, and have requests routed to those actions in order to render a template or redirecting somewhere else. ActionController::Base
inherits from a parent class ActionController::Metal
which in turn inherits from AbstractController::Base
. ActionController::Metal
isn't very interesting so we won't spend too much time on it. The in-line source comments describes it as:
... the simplest possible controller, providing a valid Rack interface without the additional niceties provided by ActionController::Base
Circling back to ActionController::Base
, the class itself doesn't actually define many interesting methods. Instead ActionController::Base
is a composite of various modules such as UrlFor
, Redirecting
, HttpAuthentication
, Logging
, Cookies
that are loaded into the class at require time. One module in deserving of our focus is AbstractController::Rendering
.
The AbstractController::
module provides Rendering
ActionController
a variety of handy methods including the render
method we know and love.
def render(*args, &block)
options = _normalize_render(*args, &block)
rendered_body = render_to_body(options)
if options[:html]
_set_html_content_type
else
_set_rendered_content_type rendered_format
end
_set_vary_header
self.response_body = rendered_body
end
class Posts < ApplicationController
def new
render 'new'
end
end
In addition to render
, the module also contains a method called view_assigns
. The view_assigns
method is pretty quirky:
# This method should return a hash with assigns.
# You can overwrite this configuration per controller.
def view_assigns
variables = instance_variables - _protected_ivars
variables.each_with_object({}) do |name, hash|
hash[name.slice(1, name.length)] = instance_variable_get(name)
end
end
Basically, it calls the Ruby base Object
method instance_variables
which returns an Array of all the currently defined instance variables for an object and creates a hash mapping their names to their values. Now, recall that AbstractController::Rendering
is a module that is mixed in the class ActionController::Base
which is what your concrete controller implementation inherits from. This means that if view_assigns
is invoked from your controller all the currently defined instance variables will be assigned to this hash. Interesting.... We've discovered how the instance variables are captured but how does the controller connect to the view?
ActionView
Returning to our previous note about Rails being built from "Action" type gems, view and templating logic live in a fun little gem called ActionView
. ActionView
is responsible for understanding how to render different template engines like embedded ruby, and HTML. To draw the lines of responsibility a bit more clearly, ActionController
can tell us what to render, but, it does not know how to render it. That's ActionView
's job.
The base class for ActionView
is somewhat anti-climatically named ActionView::Base
. The class itself does quite a bit of serious business. The job of hierarchal template rendering doesn't sound like a laughing matter (but then again maybe it is? DHH seems to have a lot of fun on Twitter). Anyway, when an ActionView
is instantiated it eventually calls an important method named assign with a payload called assigns
:
def assign(new_assigns) # :nodoc:
@_assigns = new_assigns.each do |key, value|
instance_variable_set("@#{key}", value)
end
end
The ActionView::Base
assign
method is responsible for taking the new_assigns
argument iterating through it to define instance variables using the instance_variable_set
method. These instance variables are named, assigned and set in the template object using the key and value pairs in the new_assigns
hash. This means that the template object rendered will have access to these variables immediately after being instantiated. Does this look familiar?
<% @my_index_var.each do |i| %>
<p> Number: <%= I %> </p>
<% end %>
Ok so that all makes sense but how and where does the new_assigns
payload come from?
ActionView Take Two
In the same ActionView
gem lives a module called ActionView::Rendering
where a view's "context" is built before it is rendered to the screen. In specific, the ActionView::Rendering
module has a public hook method called render_to_body
which is invoked by ActionController
when the render
method is called. Under the hood render_to_body
calls a private method _render_template
:
def _render_template(options)
variant = options.delete(:variant)
assigns = options.delete(:assigns)
context = view_context
context.assign assigns if assigns
lookup_context.variants = variant if variant
rendered_template = context.in_rendering_context(options) do |renderer|
renderer.render_to_object(context, options)
end
rendered_format = rendered_template.format || lookup_context.formats.first
@rendered_format = Template::Types[rendered_format]
rendered_template.body
end
Now, _render_template
does quite a number of things but the important line for us to close in on is the invocation of the method view_context
.
The view_context
method is the glue that wraps everything together here:
def view_context
view_context_class.new(lookup_context, view_assigns, self)
end
It instantiates a new ActionView::Base
object (view_context_class
) and passes it an assigns
hash consisting of... a call to the view_assigns
method from AbstractController::Base
!!
This means that a hash of the currently defined instance variables for the controller that called render will be passed along as an argument to the ActionView::Base
initialize
method. Nice! We've found the source. Here is the the above words in image form:

Phew... quite the journey but we've pulled the curtains back on one of the more opaque parts of the Ruby on Rails framework and have made it through in one piece.
Have fun!