2.13 Linking Pages with Layouts

Before you tackle the rest of the project on your own, it would now be nice to add some links (like in our target), so we don’t need to keep typing in the URL addresses.

Start with our user_paper.html.erb view template:

<!-- app/views/game_templates/user_paper.html.erb -->

<div>
  <a href="/rock">Play Rock</a>
</div>
<div>
  <a href="/paper">Play Paper</a>
</div>

<h2>
    We played paper!
</h2>

<h2>
    They played <%= @comp_move %>!
</h2>

<h2>
    We <%= @outcome %>!
</h2>

{: mark_lines=“3-8”}

Now we should see links at /paper. But, if we click on “Play Rock”, and we are taken to the /rock URL, then the links are not there. Because we only put them in the user_paper.html.erb file, and not in the user_rock.html.erb file, which is what visiting the /rock URL will render.

How can we avoid repeating these HTML navigation links in all of our view templates?

Here is one of the great benefits of working in Rails instead of HTML. We are dynamically generating responses rather than hard-coding into a bunch of files. If there is common stuff we want on every page, like a navbar or footer, then we can make a special file in app/views/layouts/.

Create a file in that folder called wrapper.html.erb and fill it with:

<!-- app/views/layouts/wrapper.html.erb -->

<div>
  <a href="/rock">Play Rock</a>
</div>
<div>
  <a href="/paper">Play Paper</a>
</div>
<div>
  <a href="/scissors">Play Scissors</a>
</div>

<%= yield %>

<div>
  <a href="/">Rules</a>
</div>

Copyright, Appdev. All rights reserved.

{: mark_lines=“13”}

Every page will now have all of its contents placed in the location of <%= yield %>, and everything above and below this will be rendered on that page. yield is one of many special Ruby objects that we have access to in our Rails application. We just need to also change our application_controller.rb file to note this, and have it take effect:

# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  layout("wrapper.html.erb")
  ...

{: mark_lines=“4”}

This inherited We never defined it in our ApplicationController or anywhere else. So we know it is coming from < ActionController::Base. layout method takes one argument. It already knows to look in the folder app/views/layouts/ for the file that we created.

After this change, try visiting your pages using the links. It should work, except for /scissors, because we haven’t done the RCAV yet.

If we only wanted the layout to apply on a per-page basis we could also leave layout(false) in the controller. Then, in the play_rock method (action), we could change our render statement to:

render({ :template => "game_templates/user_rock.html.erb", :layout => "wrapper.html.erb" })

This additional :layout argument would then only put the navbar and footer from wrapper.html.erb on the /rock route.

Time for a rails grade and a /git commit!

2.13.1 Completed Code

<!-- app/views/layouts/wrapper.html.erb -->

<div>
  <a href="/rock">Play Rock</a>
</div>
<div>
  <a href="/paper">Play Paper</a>
</div>
<div>
  <a href="/scissors">Play Scissors</a>
</div>

<%= yield %>

<div>
  <a href="/">Rules</a>
</div>

Copyright, Appdev. All rights reserved.
# app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  layout("wrapper.html.erb")
  ...