Ruby

How does yield work in application.html.erb

How does yield work in application.html.erb
In: Ruby

You'll encounter this in new Rails apps inside your application.html.erb:

<body>  
  <main>  
    <%= yield %>  
  </main>  
</body>

But how does yield work inside of the layout template? Where does it get executed and how does it know to yield the right view in between the layout?

First of all, you'd need to understand Ruby's yield syntax itself. There's a lot of good material about it in the Internet, but in short:

You define a method that takes a block:

def got_it(&block)
  puts "before yield"
  yield
  puts "after yield"
end

And when executing that method, you give it a block as you do with other common methods like .each { puts "block inside of curly braces" } or .map do "also a block" end.

So in our case we do:

> got_it { puts "in between yield" }

Ruby-Block-Flow-copy

Being equipped with that knowledge, it might mean that somehow application.html.erb gets called with a block that contains the to be displayed view contents which then get yielded between the layout template's <main> tags.

So let's go a step back to the beginning and see how this actually plays out in Rails app and where things are happening.

  1. Client issues HTTP request
  2. A route gets hit
  3. A Controller action gets hit
  4. There is an implicit or explicit render call

E.g. here:

# GamesController
def index
  @games = Game.all
  # Here render gets called implicitly if you don’t write it yourself.
end

Now render is quite a beast. I've dug the relevant stuff in the Ruby Guides Layouts and Rendering Section and had a few pointers, so I tried looking into where the flow of render leads in the AbstractController::Rendering module. That's a module included in every controller that inherits from ActionController::Base. So basically most controllers that you'll see in the wild.

The Ruby Guides also mention that the logic for rendering stuff is in ActionView::Template::Handlers.

There's tooooons of stuff happening with a lot of moving parts. So let's try to simplify this a bit.

Ruby distributions have ERB included by default:

$ irb
> require 'erb'
=> true
> ERB.new("2 + 2 is <%= 2 + 2 %>").result
=> "2 + 2 is 4"

> ERB.new("YOUR views.html.erb files get transformed to strings here")

That's it, all your template rendering is based on this kind of code πŸ‘† Somewhere deep down in the render method hierarchy, ERB takes some input from you (your ".html.erb" files) and turns it into a String which is sent back to the user as a document response via HTTP.

But Rails is cooler than ERB, they don't use ERB directly anymore but Erubi, a different gem that is said to be faster and more robust. Basically, Erubi is a slightly different and optimized implementation of 'erb'.

The above example in Erubi is a bit different, a bit more eval:

$ gem install eruby
$ irb
> require 'erubi'
> eval(Erubi::Engine.new("2 + 2 is <%= 2 + 2 %>").src)

Let's tie all of this together now. We said there is this process:

  1. Client issues HTTP request
  2. A route gets hit
  3. A Controller action gets hit
  4. There is an implicit or explicit render call

Now we know that render brings things together. It takes the view name, renders the layout and passes the view as a block to the layout. Let's look at an simplified code example to illustrate it.

In some of our controllers, something like this happens:

def index
  @games = Game.all
  # render(:index) is called implicitly here.
end

This is how render could look like in its simplest form:

def render(template)  
  render_with_layout do  
    render_template(template)  
  end  
end

It runs a render_with_layout method that takes a block where render_template generates the template HTML string for the requested :index view.

def render_with_layout  
  # This could come from some ERB file, like application.html.erb 
  default_layout = <<-END  
    <head> ... lots of heady stuff </head>
    <body>
      #{yield}  
    </body>
  END
  eval(Erubi::Engine.new(default_layout).src)  
end

render_with_layout takes a default layout in this case that could come from some application.html.erb but here we just store it in local variable for simplicity.

What's getting "inserted" in-place of the yield is the code from render_template (because it's returned by the block of render_with_layout { render_template }, right?):

def render_template(partial_name)  
  eval(Erubi::Engine.new(File.read("#{partial_name.to_s}.html.erb")).src)  
end

So, you can imagine this:

  default_layout = <<-END  
    <head> ... lots of heady stuff </head>
    <body>
      #{yield}  
    </body>
  END

More like this:

  default_layout = <<-END  
    <head> ... lots of heady stuff </head>
    <body>
      eval(Erubi::Engine.new(File.read("#{partial_name.to_s}.html.erb")).src) 
    </body>
  END

And in the end, the default_layout is rendered at the end of the render_with_template:

eval(Erubi::Engine.new(default_layout).src)

Check it out in the repo if you'd like to play around with it yourself: here

So hopefully this article helps to dullify some magic and to get the gist of the initial question about how yield works in a Rails' application.html.erb.

IMG_0243


Your Turn

If you've been to any of my workshops or talks, you'll now that I'm a big fan of learning techniques: Recall, Spaced Repetition, Deliberate Practice, and Mnemonics.

Here are some exercises to pump this article into your brain if you feel like it:

Deliberate practice

Write your own render method where render_with_layout takes a :custom_layout parameter. You might notice that having the default_layout inside of an application.html.erb is tougher to implement. You can make use of methods though, if you'd like to keep things simple.

Recall

Now it gets a bit esoteric, but after completing this article, close your eyes and do a mental exercise. Imagine a request calling a show action on your favorite resource. Go the way down from routes over the controller up to the point where the whole yielding stuff happens. Do you see the flow?

Spaced Repetition

Add a reminder to repeat the above recall exercise again in 3 days.


References

If you'd like to dig even deeper yourself, use some resources and examples that I used for this post:

erubi examples with yield and blocks:


Comments or questions? Just tweet it out to me!

Comments
More from RichStone Input Output
Great! You’ve successfully signed up.
Welcome back! You've successfully signed in.
You've successfully subscribed to RichStone Input Output.
Your link has expired.
Success! Check your email for magic link to sign-in.
Success! Your billing info has been updated.
Your billing was not updated.