Implicit Layout rendering in Rails

Using layouts to uncover your application's boundaries

Featured on Hashnode

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

A wireframe of a care-share app with 4 pages: a sign up page with a menu at the top with links to home, about, pricing sign up and log in, a logged in section with a menu to the right with links to a dashboard, search, bookings and my account and a content area to the right. A search page with no navigation and a lot of filters, search results and a map and finally a page for the car with several tabs for information, location, features and pictures.

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:

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.

Left: wireframe of the sign up page with a top navigation and a sign up form. The top links read home, about, pricing, sign up and log in. Right: wireframe of the app after the user has logged in with a left-hand side navigation with links to a dashboard, search, bookings and my account and a right-hand content area.

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.

ControllerLayout
ApplicationControllerapplication.html.erb
Public::MainControllerpublic/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 from

  • Public::MainController that inherits from

  • ApplicationController

So when rendering the Public::RegistrationsController#new action, Rails will perform the following lookup:

ControllerLayoutFound?
A specific layout for the controllerPublic::RegistrationsControllerpublic/registrations.html.erb
A layout for its parent controllerPublic::MainControllerpublic/main.html.erb
A layout for its grandparent controllerApplicationControllerapplication.html.erb(it's there but it doesn't look for it because it already found one)

Git commit

Left: wireframe of the sign up page with a top navigation and a sign up form. The top links read home, about, pricing, sign up and log in. Right: wireframe of the app after the user has logged in with a left-hand side navigation with links to a dashboard, search, bookings and my account and a right-hand content area.

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...

A wireframe showing 3 screens of the authenticated portion of the app. The main navigation layout with links to the dashboard, search and booking pages, one of the search experience with search filters, results and a map and a third one, showing a specific car a user chose to view.

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!