wasabigeek

Customising Generator Templates in Rails (Gems too!)

November 07, 2020

Rails’ generators are a great boon to the Indie HackerTM or Solo DeveloperTM. Running rails g scaffold ... will quickly bootstrap the full stack for a model, creating the routes, controllers, views and even tests.

As you start customising and standardising your interface though, the default templates get less and less useful - most of the generated code doesn’t fit your layout or setup, and you end up deleting most of it or skipping them.

What if we could change the… templates?

Rails actually gives you a lot of control over the generators, but early on in a project, perhaps the most impactful and simple thing to do is to override the templates.

Let’s look at an example. Normally Rails would generate a show.html that looks something like this:

<p id="notice"><%= notice %></p>

<p>
  <strong>Attribute:</strong>
  <%= @object.attribute %>
</p>

<%= link_to 'Edit', edit_object_path(@object) %> |
<%= link_to 'Back', object_path %>

For a personal project, I’ve made mine generate something like:

<% content_for :breadcrumbs do %>
  <div class="...">
    Home > <%= @object.attribute %>
  </div>
<% end %>

<% content_for :heading do %>
  <div class="...">
    <h1 class="...">
      <%= @object.name %>
    </h1>
    <div>
      <%= link_to 'Edit object', edit_object_path(@object), { class: '...' } %>
    </div>
  </div>
<% end %>

<% content_for :main_content do %>
  <div class="...">
    <%= @object.attribute %>
  </div>
<% end %>

It’s saved me a lot of time!

To do so, simply add a template to your Rails project in lib/templates/erb/scaffold/show.html.erb. You can use the original as a starting point. The other templates can also be easily changed following a similar path. Note the double percentages (%%) used to escape ERB tags!

Bonus: Gems too!

This also works for your gems that have generators (e.g. rspec-rails or react-rails), though it’s not obvious where to place the templates.

Our answer lies in Rails::Generators::Base.inherited:

def self.inherited(base) #:nodoc:
  ...
  Rails::Generators.templates_path.each do |path|
    if base.name.include?("::")
      base.source_paths << File.join(path, base.base_name, base.generator_name)
    else
      base.source_paths << File.join(path, base.generator_name)
    end
  end
  ...
end

So, in addition to the source_root that most Generators would define, Rails will also search a few extra paths. In general, you should place the template in lib/templates/<top_level_module>/<generator_class_prefix>/<template_name>.

For example, react-rails has React::Generators::ComponentGenerator, so if I wanted to replace component.es6.jsx, I would place it in lib/templates/react/component/component.es6.jsx.

I hope that helps!

Comments


Written by Nick who is a indie-hacker wannabe, living in sunny Singapore. Twitter | GitHub