DEV Community

Alexey Poimtsev
Alexey Poimtsev

Posted on • Originally published at alec-c4.com on

Rails scaffold templates

Starting from rails7, code team have changed scaffold templates and from my point of view - it is not the best change they have done. Every time I run rails g scaffold Post title:string body:text or something like this I need to change views, controller and specs to match my needs. And today I’ll show you how to customize your templates to reduce time you spend to change generated code.

Firstly, let me show you where those templates are located. If you check links below - you’ll see a source code of standard rails templates for views and controllers.

Now, let’s create our own templates. I’ll start with the scaffold controller. All you need is to create directory lib/templates/rails/scaffold_controller and put controller.rb.tt file with your template. For my needs I did some small changes - just added support for i18n to create, update and destroy actions by adding

notice: t(".create_successful")
Enter fullscreen mode Exit fullscreen mode

to appropriate places.

Next, let’s change our view templates. I don’t like current ones so I took templates from rails 6.x and updated it for my needs (for bootstrap based project I’ve taken bootstrap_form gem and have changed ujs-dependent code to use hotwire). I’ve created directory lib/templates/erb/scaffold and placed my _form.html.erb.tt, edit.html.erb.tt, index.html.erb.tt, partial.html.erb.tt, new.html.erb.tt, show.html.erb.tt to that location. You can find my code below

_form.html.erb.tt

<%%= bootstrap_form_with(model: <%= model_resource_name %>) do |form| %>
  <%% if <%= singular_table_name %>.errors.any? %>
    <div style="color: red">
      <h2><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h2>

      <ul>
        <%% <%= singular_table_name %>.errors.each do |error| %>
          <li><%%= error.full_message %></li>
        <%% end %>
      </ul>
    </div>
  <%% end %>

<% attributes.each do |attribute| -%>
  <div>
<% if attribute.password_digest? -%>
    <%%= form.password_field :password %>
  </div>

  <div>
    <%%= form.password_field :password_confirmation %>
<% elsif attribute.attachments? -%>
    <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, multiple: true %>
<% else -%>
    <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %> %>
<% end -%>
  </div>

<% end -%>
  <div>
    <%%= form.primary %>
  </div>
<%% end %>
Enter fullscreen mode Exit fullscreen mode

edit.html.erb.tt

<h1>Editing <%= human_name.downcase %></h1>

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

<br>

<div>
  <%%= link_to "Show this <%= human_name.downcase %>", <%= model_resource_name(prefix: "@") %> %> |
  <%%= link_to "Back to <%= human_name.pluralize.downcase %>", <%= index_helper(type: :path) %> %>
</div>
Enter fullscreen mode Exit fullscreen mode

index.html.erb.tt

<h1><%= human_name.pluralize %></h1>

<table class="table" id="<%= plural_table_name %>">
  <thead>
    <tr>
<% attributes.reject(&:password_digest?).each do |attribute| -%>
<% if attribute.attachment? -%>
    <th>Download</th>
<% elsif attribute.attachments? -%>
    <th>Downloads</th>
<% else -%>
    <th><%= attribute.human_name %></th>
<% end -%>
<% end -%>
    <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <%%= render @<%= plural_table_name %> %>
  </tbody>
</table>

<%%= link_to "New <%= human_name.downcase %>", <%= new_helper(type: :path) %>, class: "btn btn-primary" %>
Enter fullscreen mode Exit fullscreen mode

partial.html.erb.tt

<tr>
<% attributes.reject(&:password_digest?).each do |attribute| -%>
    <td>
    <% if attribute.attachment? -%>
        <%%= link_to <%= singular_name %>.<%= attribute.column_name %>.filename, <%= singular_name %>.<%= attribute.column_name %> if <%= singular_name %>.<%= attribute.column_name %>.attached? %>
    <% elsif attribute.attachments? -%>
        <%% <%= singular_name %>.<%= attribute.column_name %>.each do |<%= attribute.singular_name %>| %>
          <div><%%= link_to <%= attribute.singular_name %>.filename, <%= attribute.singular_name %> %></div>
    <% else %>
        <%%= <%= singular_table_name %>.<%= attribute.column_name %> %>
    <% end %>
    </td>
<% end -%>
        <td><%%= link_to 'Show', <%= model_resource_name %>, class: "btn btn-light btn-sm" %></td>
        <td><%%= link_to 'Edit', edit_<%= singular_route_name %>_path(<%= singular_table_name %>), class: "btn btn-warning btn-sm" %></td>
        <td><%%= link_to 'Destroy', <%= model_resource_name %>, data: {turbo_method: :delete, turbo_confirm: 'Are you sure?'}, class: "btn btn-danger btn-sm" %></td>
</tr>
Enter fullscreen mode Exit fullscreen mode

new.html.erb.tt

<h1>New <%= human_name.downcase %></h1>

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

<br>

<div>
  <%%= link_to "Back to <%= human_name.pluralize.downcase %>", <%= index_helper(type: :path) %> %>
</div>
Enter fullscreen mode Exit fullscreen mode

show.html.erb.tt

<% attributes.reject(&:password_digest?).each do |attribute| -%>
<p>
  <strong><%= attribute.human_name %>:</strong>
<% if attribute.attachment? -%>
  <%%= link_to @<%= singular_table_name %>.<%= attribute.column_name %>.filename, @<%= singular_table_name %>.<%= attribute.column_name %> if @<%= singular_table_name %>.<%= attribute.column_name %>.attached? %>
<% elsif attribute.attachments? -%>
  <%% @<%= singular_table_name %>.<%= attribute.column_name %>.each do |<%= attribute.singular_name %>| %>
    <div><%%= link_to <%= attribute.singular_name %>.filename, <%= attribute.singular_name %> %></div>
  <%% end %>
<% else -%>
  <%%= @<%= singular_table_name %>.<%= attribute.column_name %> %>
<% end -%>
</p>

<% end -%>
<%%= link_to 'Edit', edit_<%= singular_table_name %>_path(@<%= singular_table_name %>) %> |
<%%= link_to 'Back', <%= index_helper %>_path %>
Enter fullscreen mode Exit fullscreen mode

And the last step - let’s update rspec template for model. In my example I’ll create a template for model spec with sections fields (to put field-specific tests, such as counter cache), associations (has_many, belongs_to etc), validations and methods. All I need it to create file lib/templates/rspec/model/model_spec.rb.tt with following

require "rails_helper"

<% module_namespacing do -%>
RSpec.describe <%= class_name %>, <%= type_metatag(:model) %> do
  describe "fields" do
    pending "add some field tests (such as counter_cache) to (or delete) #{ __FILE__ }"
  end

  describe "associations" do
    pending "add some association tests to (or delete) #{ __FILE__ }"
  end

  describe "validations" do
    pending "add some validation tests to (or delete) #{ __FILE__ }"
  end

  describe "methods" do
    pending "add some method tests to (or delete) #{ __FILE__ }"
  end
end
<% end -%>
Enter fullscreen mode Exit fullscreen mode

Now I’m happy, isn’t it awesome? :)

Looking for more? Check my blog https://alec-c4.com

Top comments (0)