How to utilize Rails layouts to keep our view templates less clutter.
The word layouts should be pretty common to those who have been working on rails projects for while. If you are new to rails like me and have heard of layouts but do not know how to implement it in your project, then you may want to continue what you are doing at this moment. As default, rails creates Layouts folder under app/views subdirectory when you first create your new project with ‘rails new <project_name>’ command. Rails, also creates ‘application.html.erb’ file by default under the Layouts folder. This file contains the basic template as shown in below picture. As you can see on the picture, a layout is nothing but just an HTML document container and it is meant to be reused by controllers for your views. Since, this template shares its name with the ApplicationController, which is typically extended by all the other controllers in an application, it is therefore used as default layout for all views. The uses of layouts become more and more common as your project grows in size. A basic application may have one layout shared by all actions, while more complex applications may have many layouts, even more than one per controller. One of the primary goal of utilizing layouts is to implement one of our core principles, ‘DRY’ (don’t repeat yourself) especially on the view templates pages. As we know most pages on a website are going to share a common structure: the header, the footer, the style sheet, and possibly other side items, like navigation menus or sidebars, those areas are the perfect candidates for layouts.
Before jumping on how to use layouts in our application, let’s try to understand the above default template. Notice the line with the keyword ‘yield’. That’s where our layout yields to our template which means our template content from our views are dropped where the yield statement is declared. If you’re still not sure how yield works, then let’s make it clear with the following diagram:
In the above picture, everything on the left most side of the screen is our browser outputs running on localhost. Images with black background are our codes. Below are the steps involved in outputting above screen:
In above example, when we enter url- "http://localhost:3000/authors" in our browser:1. Our server receives request and the request will be routed to the
index action of AuthorsController.2. The index action will perform all the actions defined within it's
method (in our case, it will contact Author model to retrieve a
list of all authors: @authors = Author.all)3. Once the AuthorsController action has been completed, it will
look for the index template in it's corresponding views folder,
grab the template if it exists and look for a layout file to drop
the template.4. As mentioned earlier, all controllers will look for
‘application.html.erb’ layout file by default but in our case,
the AuthorsController will look for our custom layout file
'administrator.html.erb' because we told the controller to look
for this file for layout. (Don't worry about custom layout and
how to tell controllers to use it - it will be explained very
shortly)5. Once the controller finds the 'administrator.html.erb', the page
will be rendered in a ✌️sequential✌️manner. As you can see on
this layout page, when it comes across the statement
<%= render 'layouts/topbar' %> which is before the 'yield
statement', it will first go ahead and look for the topbar file
inside the layouts folder and render the page. That's why we see
the output "I am the top bar" before any other output strings.6. The next statement is 'yield' on our custom layout file and as
soon as the execution control hits this statement, the index
template will be dropped right on the spot. That is why, we see
the authors list below the topbar string.7. After yield, the next line of statement is
<%= render 'layouts/bottom_bar' %> which will be rendered and
that's the reason we see the string "I am the bottom bar" on the
bottom of the page.In the above case, the topbar and bottom_bar files will be rendered every time the administrator layout file is called. So, this technique can be used in an application where we have static top bar, footer or sides bar those need to be displayed on every page.
As we just saw above that we can have our own custom layout in our application, let’s go ahead and explore how we can define one and use it:
1. Add a new layout file under the layouts folder and let's call it
'administrator.html.erb'2. Now, to tell our controllers to use this new layout file, simply
go to the controller class and enter the statement:
layout '<new layout name>'. In our case:
layout 'administrator' on the top before our controller actions.3. Now, for demonstration purpose, drop the following lines of HTML
inside the administrator layout page:<!--app/views/layouts/administrator.html.erb--><!DOCTYPE html>
<title>My book store</title>
<h1>Hello from custom layout</h1>
<%= yield %>
Now, run the localhost server (rails server) and notice the page title has been changed to “My books store” and the body with the <h1> text “Hello from custom layout”. This confirms that our controller(s) are using our custom layout file.
Now that you know how to use your own layout file, let’s go ahead and identify the areas in our ‘My books store’ app templates where we might have code repetition. In our example, below code snippets are almost identical except the instance variables on which the same methods are being called. These identical code snippets are used both on the new author and new book creation templates as shown below.
<!--app/views/authors/new.html.erb--><% if @author.errors.any? %>
<% @author.errors.full_messages.each do |message| %>
<li> <%= message %> </li>
<% end %>
<%end%>----------------------------------------------------------------<!--app/views/books/new.html.erb--><% if @book.errors.any? %>
<% @book.errors.full_messages.each do |message| %>
<li> <%= message %> </li>
<% end %>
Moreover, the new form template will also be shared by our edit template on both authors and books views. This seems like a perfect candidate to bring the strength of our layout and partial to the rescue. So, in order to remove these duplicates, we could create a form partial on each of the view (authors and books) and render the form on new and edit templates. But, just by moving these codes to the partial forms, I don’t think our codes would be fully dry as the errors messaging code block is still repeating on all our partial forms. Since, this errors messaging code block could be utilized by many templates within our application, we could create another partial inside our layout folder, yes you read it right — layout folder, not inside the templates folder this time. Let’s name our new partial, _errormsg.html.erb and drop our errors message code block inside this partial. But, let’s make sure not to call the .errors method on any specific instance so that this partial can be reused by other templates as well. Here’s how I would like to handle these repetitions.
Note, the .errors method is called on the ‘object’ inside the _errorsmsg partial and we are passing our specific instance when we are rendering the _errormsg partial from our form partials.
app/views/layouts/_errorsmsg.html.erb<% if object.errors.any? %>
<% object.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
<% end %>app/views/books/_form.html.erb<%= render "layouts/errormsg", :object => @book %><%= form_for @book do |f| %>
<%= f.label :title %>
<%= f.text_field :title %>
<%= f.label :pages %>
<%= f.text_field :pages %>
<%= f.submit %>
<% end %>app/views/authors/_form.html.erb<%= render "layouts/errormsg", :object => @author %><%= form_for @author do |f| %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.label :birth_year %>
<%= f.text_field :birth_year %>
<%= f.submit %>
<% end %>
If you now look at your templates, there are just few lines of codes making them clean and readable.
Before I end this post, I would like to bring your attention on something cool that rails does which seems like magical. If you remember, I have placed ‘air quote’ emoji somewhere on the top of the page where I mentioned that a “layout page is rendered sequentially …”. Well, it doesn’t seem 100% true.
Let’s take a look at below scenario:
<%= link_to "New Author", new_author_path%>
<% @authors.each do |author| %>
<%= link_to author.name, author %> (b. <%=author.birth_year%>)
<%@page_footer = "I thought I will be displayed at the bottom of the authors list! 😕😕🤷🤔" %><!--app/views/layouts/administrator.html.erb--><!DOCTYPE html><html><head><title>My book store</title></head><body>
<%= @page_footer %><%= render 'layouts/topbar' %><%= yield %><%= render 'layouts/bottom_bar' %></body></html>
On the authors index template, I have declared an instance variable @page_footer at the bottom and assigned some strings. On the same administrator layout page from earlier, I have called this instance variable like this <%= @page_footer %> right below the body tag but above the yield statement. The @page_footer instance variable hasn’t been defined anywhere on the controllers, it was defined and initialized only on the index template. Now, when I ran this app, I see the content of @page_footer at the top of the screen as shown in above image. Think about it for a second. I’m using at page_footer at the top of the body tag. The yield to the template takes place few steps down in the layout, and that’s where the template is called and rendered. So, how is it possible that the @page_footer variable is available in the code up above?
I will leave this up to you to answer. Please feel free to leave comments or suggestions.