Implicit Layout rendering in Rails
Using layouts to uncover your application's boundaries
I love car-sharing services. It has allowed me to live car-free for the past three and a half years cutting down on maintenance, taxes, stress ... you name it. I can also book small cars for short rides, vans for moving stuff and larger, more comfortable cars for trips. It's amazing!
Today, we'll use a hypothetical car-share app to learn how to use Action View layouts to their fullest.
Designing a car-sharing service
This wireframe shows four basic screens our app will have that show different areas of our application that we will want to treat a bit differently.
Conditional spaghetti 🍝
One way to achieve all of these different layouts is to squeeze everything into the default application.html.erb
layout:
<!DOCTYPE html>
<html>
<head><!-- ... --></head>
<body>
<%= if current_user.signed_in? %>
<%= if current_page?(booking_rides_path) %>
<%= yield %>
<% end %>
<%= if current_page?(dashboard_path) %>
<%= yield %>
<% else %>
<%= yield %>
<% end %>
<% else %>
<!-- Public nav-->
<%= yield %>
<% end %>
</body>
</html>
Some pages require the user to log in, some don't. Some require their own specific set of rules and others share some visual aspects with others.
As you can probably tell, this will be hard to understand, extend and maintain. The issue with using a single layout is not just a matter of the ease of readability though. You could fix readability by turning most of this logic into several partials and call it a day. However this is a signal, and one you can train yourself to identify.
Identifying the problem
All of those conditionals are telling you something: There are several concepts here that are claiming their own space. It's an architectural problem! To allow our app to grow and have the flexibility to use different layouts we need to give it proper foundations for it to scale.
As I see this wireframe I'm thinking:
The app has public-facing, non-authenticated pages and other private, authenticated ones
Within the authenticated pages:
There's a design that houses links to most sections of the app like the dashboard, bookings and my account
There is a special case when searching where all navigation disappears and the user is fully focused on the search and car-choosing experience.
How does Rails find a layout
Before diving into this, remember that Rails Guides are probably the best and probably most underutilised resource to learn about the framework. Most of what I'll tell you about here you can find in the Layouts and Rendering in Rails guide.
As with most things Rails, the act of finding a layout that will wrap a template/view rendered by a controller action is based on conventions. In a fresh Rails app there is a single layout: app/views/layouts/application.html.erb
. This is the last fallback the framework will use to display a template. Because in a new app there aren't other layouts Rails uses this one.
You can make your controllers render a different layout by doing so explicitly or implicitly.
If you haven't thought much about it and just use the application.html.erb
layout then you are already using implicit rendering which relies on inheritance (more on this later).
On the other hand, you can tell Rails when to use a specific layout by:
Giving it as an argument to the
render
method
I use all of these in different situations but today we will focus on implicit layout rendering leveraging inheritance because this will help you not only organise your views but also give you the tools to build a better architectural foundation for your app.
Authenticated or not authenticated
Let's focus on the first thing we identified. The app's needs are strikingly different for the authenticated and non-authenticated sections of it. It's almost as if they were two different apps and it is usually very obvious when looking at the design.
Beyond the ApplicationController
The typical ApplicationController
looks something like this:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
before_action :authenticate_user!
end
Before every controller action, you perform some type of user authentication. But, there is a portion of your app that is not authenticated and there are multiple pages that fit in that space like sign up, sign in, password recovery etc. For all of these pages, you would need to call skip_before_action :authenticate_user!
. That's very cumbersome and also error-prone. So instead of skipping this action on every controller, why not create a specific controller that all public pages will use?
# app/controllers/public/application_controller.rb
class Public::MainController < ApplicationController
skip_before_action :authenticate_user!
end
I chose the name Main
in this article to make it obvious that it's different from Application
but you can name it whatever you want like:
Public::MainController
Public::ApplicationController
Public::BaseController
Now every controller that is part of the public portion of your application and needs to skip authentication will be able to use this as its parent controller:
class Public::RegistrationsController < Public::MainController
def new
# renders the sign up page
end
def create
# ...
end
end
At this stage, we still have a single application.html.erb
layout. Whenever we visit our new registrations controller it will use that one because remember that this is the ultimate fallback that Rails uses if no other options are provided.
Implicit Layouts
The benefit of this technique is that now we can rely on Rails' conventions to have a new, specific layout for your public controllers. We can create a main.html.erb
layout under app/views/layouts/public
. Notice the name: main.html.erb
. It takes after the name of the controller which explains why in a fresh Rails app the default layout is named application.html.erb.
Controller | Layout |
ApplicationController | application.html.erb |
Public::MainController | public/main.html.erb |
app/
controllers/
application_controller.rb
public/
✨ main_controller.rb ⬅ Our new Controller
registrations_controller.rb
views/
layouts/
application.html.erb
public/
✨ main.html.erb ⬅ 👀 A layout with matching name ✨
Rails chooses the layout to render a view in a cascading way based on the controller's inheritance chain. For the Public::RegistrationsController
its chain looks like:
Public::RegistrationsController
that inherits fromPublic::MainController
that inherits fromApplicationController
So when rendering the Public::RegistrationsController#new
action, Rails will perform the following lookup:
Controller | Layout | Found? | |
A specific layout for the controller | Public::RegistrationsController | public/registrations.html.erb | ❌ |
A layout for its parent controller | Public::MainController | public/main.html.erb | ✅ |
A layout for its grandparent controller | ApplicationController | application.html.erb | (it's there but it doesn't look for it because it already found one) |
Git commit
This feels like a good place to commit. Through the wireframes and the spaghetti application.html.erb
we saw earlier, we uncovered an important architectural pillar of our app: authenticated and non-authenticated pages have different needs and through different controllers and layouts we can convey those needs and give each an organised place to grow.
On the next episode...
In the next article, we'll tackle the next piece of our puzzle: organising the authenticated portion of our app.
Stay tuned and let me know what you think!
Off the Rails
This is the first article I posted after announcing on Twitter that I would start creating content to give back to the community and the response was amazing! Over one hundred people subscribed to my newsletter in the first couple of days. Thanks to everyone who subscribed, liked and retweeted!
Also, the car-sharing theme is part of this whole idea of designing beautiful cities and it's a topic a really enjoy. If you do too, take a look at this video from Not Just Bikes on car-sharing. This channel is AMAZING!