How to Create Custom Scaffold Templates in Ruby on Rails

Ruby on Rails is such a powerful framework. It has its own CLI of which harnesses what are known as “generators”. These generators can quickly scale up resources for your app with a few keystrokes. In this post learn how to create custom scaffold templates in Ruby on Rails

Conventions get you far

Having made quite a few brand new Ruby on Rails apps in my time I always find the default look and feel of scaffolding out a resource a bit lackluster. By default, Rails generates a ton of files and stylesheets associated with a given model you pass on the command line when creating a new scaffold.

Example:

$ rails generate scaffold Post title:string body:text

The command above will create an entire CRUD-like concept around the Post model in a given rails application. To do this it generates a number of files which include everything from stylesheets to test-specific files. It also creates an Active Record migration file which will later be migrated into a database. This ultimately means a new table is born called posts.

This concept is a huge time saver. This is especially true if you’re just prototyping an idea or testing a theory.

Extending conventions

Within a typical scaffold, you get an entire view folder with associated views for the restful routing that gets created.

app/views/posts/_form.html.erb
app/views/posts/edit.html.erb
app/views/posts/index.html.erb
app/views/posts/new.html.erb
app/views/posts/show.html.erb

Here we get the bare bones views with the logic they need to make the app work as intended. Having run so many scaffolds in my time I’ve come to realize I hate the default look and feel. Can this be changed? The answer to that is YES!

Custom scaffold view templates

Inside your app you’ll find a lib directory. By default it would probably have assets and tasks folders but we need to create a new templates directory.

The structure ends up like the following:

lib/
-- templates
  -- erb
    -- scaffold
      --| _form.html.erb.tt
      --| edit.html.erb.tt
      --| index.html.erb.tt
      --| new.html.erb.tt
      --| show.html.erb.tt

The new files with .tt extensions may look new. That’s because they are! These are a new type of view that classify as templates. When a new scaffold is run, the framework will find these and use them to scaffold out the new views that get generated.

Check out the index view:

<!-- lib/templates/erb/scaffold/index.html.erb.tt -->

<div class="container mx-auto my-8 px-4">
  <div class="flex justify-between items-center mb-4">
    <h1 class="h2"><%= plural_table_name.titleize %></h1>

    <%% if @<%= plural_table_name %>.exists? %>
      <%%= link_to 'New <%= singular_table_name.titleize %>', new_<%= singular_route_name %>_path, class: "btn btn-default" %>
    <%% end %>
  </div>

  <%% if @<%= plural_table_name %>.exists? %>
    <div class="bg-white rounded shadow">
      <table class="w-full">
        <thead>
          <tr>
<% attributes.reject(&:password_digest?).each do |attribute| -%>
            <th class="p-3 uppercase text-left text-xs text-gray-700"><%= attribute.human_name %></th>
<% end %>
            <th class="p-3 uppercase text-left text-xs text-gray-700">Actions</th>
          </tr>
        </thead>

        <tbody>
        <%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %>
          <tr class="group border-t border-gray-400 hover:bg-gray-100">
            <% attributes.reject(&:password_digest?).each do |attribute| -%>
              <td class="p-3"><%%= <%= singular_table_name %>.<%= attribute.column_name %> %></td>
            <% end %>
            <td>
              <%%= link_to "View", <%= singular_table_name %>, class: "btn btn-default" %>
              <%%= link_to "Edit", edit_<%= singular_table_name %>_path(<%= singular_table_name %>), class: "btn btn-default" %>
            </td>
          </tr>
        <%% end %>
        </tbody>
      </table>
    </div>

  <%% else %>
    <div class="bg-white rounded shadow flex items-center justify-between p-8">
      <div class="flex-1 text-center">
        <p class="text-xl font-semibold mb-4">Create your first <%= singular_table_name.titleize %></p>
      <%%= link_to 'New <%= singular_table_name.titleize %>', new_<%= singular_route_name %>_path, class: "btn btn-default" %>
      </div>
    </div>
  <%% end %>
</div>

A number of things to note:

  • This assumes you created a new rails app using my Kickoff Tailwind template. The classes you see pertain to Tailwind CSS as well as some custom components that come by default when you create a new rails app using the template. I talk more about recent changes to the template here.

  • The <%% signs are new and essentially act as a templating preformatter when a scaffold gets run within the context of erb files.

  • Inside these templates, we have access to local variables and methods like singular_table_name or singular_route_name of which are dynamic. These will get replaced with whatever resource you’re generating. In our case, it would be post.

The edit view template

<!-- lib/templates/erb/scaffold/edit.html.erb.tt -->

<div class="container mx-auto my-8 px-4">
  <div class="max-w-xl mx-auto">
    <h1 class="text-2xl font-bold mb-6">Editing <%= singular_table_name.titleize %></h1>

    <%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %>
  </div>
</div>

The new view template

<!-- lib/templates/erb/scaffold/new.html.erb.tt -->

<div class="container mx-auto my-8 px-4">
  <div class="max-w-xl mx-auto">
    <h1 class="text-2xl font-bold mb-6">New <%= singular_table_name.titleize %></h1>

    <%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %>

    <%%= link_to 'Back', <%= index_helper %>_path, class: "link" %>
  </div>
</div>

The show view template

<!-- lib/templates/erb/scaffold/show.html.erb.tt -->

<div class="container mx-auto my-8 px-4">
  <% attributes.reject(&:password_digest?).each do |attribute| -%>
  <p class="text-lg font-semibold"><%= attribute.human_name %>:</p>
  <p class="leading-normal"><%%= @<%= singular_table_name %>.<%= attribute.name %> %></p>

  <% end -%>
  <div class="flex items-center justify-start mt-6">
    <%%= link_to 'Edit', edit_<%= singular_table_name %>_path(@<%= singular_table_name %>), class: "btn btn-default mr-4" %>
    <%%= link_to 'Back', <%= index_helper %>_path, class: "btn btn-default" %>
  </div>
</div>

The _form view template

<!-- lib/templates/erb/scaffold/_form.html.erb.tt -->

<%%= form_with(model: <%= model_resource_name %>) do |form| %>
  <%%= render "error_messages", resource: form.object %>

<% attributes.each do |attribute| -%>
  <div class="mb-6">
<% if attribute.password_digest? -%>
    <%%= form.label :password, class: "label" %>
    <%%= form.password_field :password, class: "input" %>
  </div>

  <div class="mb-6">
    <%%= form.label :password_confirmation, class: "label" %>
    <%%= form.password_field :password_confirmation, class: "input" %>
<% else %>
    <%%= form.label :<%= attribute.column_name %>, class: "label" %>
    <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, class: "input" %>
<% end %>
  </div>

<% end -%>
  <div class="mb-6 flex justify-between items-center">
    <%%= form.button class: "btn btn-default" %>

    <%% if form.object.persisted? %>
      <%%= link_to 'Delete', form.object, class: "btn btn-default", method: :delete, data: { remote: true, confirm: "Are you sure?" } %>
    <%% end %>
  </div>
<%% end %>

Let the magic begin

With these sets of templates in place, you can now create new scaffolds that will harness them instead of the defaults that come with Rails. What’s great here is that you can go as crazy or simple as you want with the template depending on your needs. In my own case, I might extend my rails application template Kickstart to use these for future screencasts/tutorials. It saves a ton of time and brain power which is the point of the framework.

I hope you found this technique useful. I find it really empowering to be able to simply add some templates and let the app/framework take it from there. There was little to no configuration which is a big selling point for Ruby on Rails and why I continue to love working with this framework every day.

Shameless plug!

I have a new course called Hello Rails. Hello Rails is a modern course designed to help you start using and understanding Ruby on Rails fast. If you’re a novice when it comes to Ruby or Ruby on Rails I invite you to check out the site. The course will be much like these builds but a super more in-depth version with more realistic goals and deliverables. Sign up to get notified today!

Follow @hello_rails and myself @justalever on Twitter.