DEV Community

Cover image for Build great views fast - Custom view scaffolding in rails with bootstrap 4
Kim Bedson
Kim Bedson

Posted on

Build great views fast - Custom view scaffolding in rails with bootstrap 4

Scaffolding views in rails can save a lot of time, however the default scaffolded views are too plain to be useful. By using custom scaffolding with bootstrap 4, it's easy to quickly create views that look good and only 5 additional files are needed!!!

From this
Alt Text
Alt Text

To this
Alt Text

Alt Text

The existing scaffolding templates are under the railties directory, located in /lib/rails/generators/erb/scaffold/templates

The following command shows where railties is located.

bundle show railties

Edit Config

in config/application.rb add

 config.generators do |g|
      g.orm             :active_record
      g.template_engine :erb
      g.test_framework  :test_unit, fixture: true
    end

Create template files

Then in the project lib directory create the following folders

template/erb/scaffold

The custom scaffolding templates are placed in this folder, and override the default scaffolding templates.

Create the following files
lib/templates/erb/scaffold/_form.html.erb.tt
lib/templates/erb/scaffold/edit.html.erb.tt
lib/templates/erb/scaffold/index.html.erb.tt
lib/templates/erb/scaffold/new.html.erb.tt
lib/templates/erb/scaffold/show.html.erb.tt

_form.html.erb.tt

<%%= form_with(model: <%= singular_table_name %>, local: true) do |form| %>
  <%% if <%= singular_table_name %>.errors.any? %>
    <div id="error_explanation" class="alert alert-danger" role="alert">
      <h5><%%= pluralize(<%= singular_table_name %>.errors.count, "error") %> prohibited this <%= singular_table_name %> from being saved:</h5>
      <ul>
      <%% <%= singular_table_name %>.errors.full_messages.each do |message| %>
        <li><%%= message %></li>
      <%% end %>
      </ul>
    </div>
  <%% end %>
  <% attributes.each do |attribute| -%>
    <% if attribute.password_digest? -%>
      <div class="form-group">
        <%%= form.label :password %>
        <%%= form.password_field :password, id: :<%= field_id(:password) %>,class:"form-control"%>
      </div>
      <div class="form-group">
        <%%= form.label :password_confirmation %>
        <%%= form.password_field :password_confirmation, id: :<%= field_id(:password_confirmation) %> ,class:"form-control" %>
      </div>
    <% elsif attribute.type == :boolean -%>
      <div class="form-group form-check">
      <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, id: :<%= field_id(attribute.column_name) %> ,class:"form-check-input"%>
      <%%= form.label :<%= attribute.column_name %>, class:"form-check-label"%>
      </div>
    <% else -%>
      <div class="form-group">
        <%%= form.label :<%= attribute.column_name %> %>
        <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, id: :<%= field_id(attribute.column_name) %> ,class:"form-control"%>
      </div>
    <% end -%>
  <% end -%>
  <div class="actions">
    <%%= form.submit "Save", class:"btn btn-success min-width-btn" %>
  </div>
<%% end %>

edit.html.erb.tt


<div class="container-fluid">
    <h1>Editing <%= singular_table_name.titleize %></h1>
    <%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %>
    <br>
    <%%= link_to 'Show', @<%= singular_table_name %>, class:"btn btn-light min-width-btn" %>
    <%%= link_to 'Back', <%= index_helper %>_path, class:"btn btn-light min-width-btn" %>
</div>

index.html.erb.tt


<div class="container-fluid">
  <h1><%= plural_table_name.titleize %></h1>
  <table class="table">
    <thead>
      <tr>
        <% attributes.reject(&:password_digest?).each do |attribute| -%>
          <th><%= attribute.human_name %></th>
        <% end -%>
        <th></th>
      </tr>
    </thead>
    <tbody>
      <%% @<%= plural_table_name %>.each do |<%= singular_table_name %>| %>
        <tr>
          <% attributes.reject(&:password_digest?).each do |attribute| -%>
            <td>
              <%%= <%= singular_table_name %>.<%= attribute.name %> %>
            </td>      
          <% end -%>
          <td>
          <%%= link_to 'Show', <%= singular_table_name %>, class:"btn btn-light min-width-btn" %>
          <%%= link_to 'Edit', edit_<%= singular_table_name %>_path(<%= singular_table_name %>),class:"btn btn-primary min-width-btn"  %>     
          <%%= link_to 'Delete', <%= singular_table_name %>, method: :delete, data: { confirm: 'Are you sure?' }, class:"btn btn-danger min-width-btn"%>
          </td>
        </tr>
      <%% end %>
    </tbody>
  </table>
  <br>
  <%%= link_to 'New <%= singular_table_name.titleize %>', new_<%= singular_table_name %>_path,class:"btn btn-success min-width-btn" %>
</div>

new.html.erb.tt

<div class="container-fluid">
    <h1>New <%= singular_table_name.titleize %></h1>
    <%%= render 'form', <%= singular_table_name %>: @<%= singular_table_name %> %>
    <br>
    <%%= link_to 'Back', <%= index_helper %>_path, class:"btn btn-light min-width-btn" %>
</div>

show.html.erb.tt


<div class="container-fluid">
  <h1><%= singular_table_name.titleize %> Details</h1>
  <%%= form_with(model: @<%= singular_table_name %>, local: true) do |form| %>
    <% attributes.each do |attribute| -%>
      <% if attribute.type == :boolean -%>
        <div class="form-group form-check">
          <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, id: :<%= field_id(attribute.column_name) %> ,class:"form-check-input", disabled:true%>
          <%%= form.label :<%= attribute.column_name %>, class:"form-check-label"%>
        </div>
      <% else -%>
        <div class="form-group">
          <%%= form.label :<%= attribute.column_name %> %>
          <%%= form.<%= attribute.field_type %> :<%= attribute.column_name %>, id: :<%= field_id(attribute.column_name) %> ,class:"form-control", readonly:true%>
        </div>
      <% end -%>
    <% end -%>
  <%% end %>
  <%%= link_to 'Edit', edit_<%= singular_table_name %>_path(@<%= singular_table_name %>),class:"btn btn-primary min-width-btn" %> 
  <%%= link_to 'Back', <%= index_helper %>_path,class:"btn btn-light min-width-btn" %>
</div>

Add Bootstrap 4

add the bootstrap 4 ruby gem to the gemfile and follow the configuration steps in https://github.com/twbs/bootstrap-rubygem

gem 'bootstrap', '~> 4.4.1'

Add additional CSS

in app/assets/stylesheets/application.scss add the following

 .min-width-btn {
    min-width:100px;
    margin:1px;
  }

Run the scaffolder

Generate new views for the restaurant model overriding the original ones.

rails g erb:scaffold Restaurant name description phone time_zone currency active:boolean

All done

Enjoy the much improved views.

Alt Text

Top comments (5)

Collapse
 
muatsoftgit profile image
Adário Muatelembe • Edited

add to lib/templates/rails/scaffold_contoller/controller.rb

<% if namespaced? -%>
require_dependency "<%= namespaced_path %>/application_controller"

<% end -%>
<% module_namespacing do -%>
class <%= controller_class_name %>Controller < ApplicationController
  before_action :set_<%= singular_table_name %>, only: [:show, :edit, :update, :destroy]
  # before_action :authenticate_user! #remove comment if you have devise
  # load_and_authorize_resource #remove comment if you have cancancan

  # GET <%= route_url %>
  def index
    @<%= plural_table_name %> = <%= orm_class.all(class_name) %>.all
    respond_to do |format|
      format.html
      format.json { render json: @<%= plural_table_name %>.as_json}
      format.js
      format.xlsx
      format.pdf
    end
  end

  # GET <%= route_url %>/1
  def show
    respond_to do |format|
      format.html
      format.json { render json: @<%= singular_table_name %>.as_json}
      format.js
      format.xlsx
      format.pdf
    end
  end

  # GET <%= route_url %>/new
  def new
    @<%= singular_table_name %> = <%= orm_class.build(class_name) %>
    respond_to do |format|
      format.html
      format.json { render json: @<%= singular_table_name %>.as_json}
    end
  end

  # GET <%= route_url %>/1/edit
  def edit
    respond_to do |format|
      format.html
      format.json { render json: @<%= singular_table_name %>.as_json}
    end
  end

  # POST <%= route_url %>
  def create
    @<%= singular_table_name %> = <%= orm_class.build(class_name, "#{singular_table_name}_params") %>
    respond_to do |format|
      if @<%= orm_instance.save %>
        format.html { redirect_to <%= singular_table_name %>_path(@<%= singular_table_name %>), notice: '<%= singular_table_name.capitalize.humanize %> registado com sucesso' }
        format.json { render json: @<%= singular_table_name %>.as_json}
      else
        format.html { render :new }
        format.json { render json: @<%= singular_table_name %>.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT <%= route_url %>/1
  def update
    respond_to do |format|
      if @<%= orm_instance.update("#{singular_table_name}_params") %>
        format.html { redirect_to <%= singular_table_name %>_path(@<%= singular_table_name %>), notice: '<%= singular_table_name.capitalize.humanize %> actualizado com sucesso' }
        format.json { render json: @<%= singular_table_name %>.as_json}
      else
        format.html { render :edit }
        format.json { render json: @<%= orm_instance.errors %>, status: :unprocessable_entity }
      end
    end
  end

  # DELETE <%= route_url %>/1
  def destroy
    @<%= orm_instance.destroy %>

    respond_to do |format|
      format.html { redirect_to <%= index_helper %>_path, notice: '<%= singular_table_name.capitalize.humanize %> eliminado com sucesso' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_<%= singular_table_name %>
      @<%= singular_table_name %> = <%= orm_class.find(class_name, params[:id]) %>
    end

    # Only allow a trusted parameter "white list" through.
    def <%= "#{singular_table_name}_params" %>
      params.require(:<%= singular_table_name %>).permit(<%= (attributes_names.map { |name| ":#{name}" }).join(', ') %>)
    end
end
<% end -%>
Enter fullscreen mode Exit fullscreen mode
Collapse
 
kylelaw profile image
Kyle-Law

Got error
/lib/templates/erb/scaffold/show.html.erb.tt:14:in block in template': undefined methodfield_id' for #Erb::Generators::ScaffoldGenerator... (NoMethodError)

Collapse
 
x1wins profile image
Chang-Woo Rhee

hello, i did check field_id method in github and apidock.com.
there is field_in method in NamedBase class, but that is 5.1 version only. i don't know why they removed field_in method.
and then we can't more use field_in. if you want still, you can make helper for field_in method that is simple code. you can check field_in method in below link.

apidock.com/rails/v5.1.7/Rails/Gen...
github.com/rails/rails/blob/5-1-st...

Collapse
 
x1wins profile image
Chang-Woo Rhee

i have same problem with @kylelaw
where can i find link for 'field_id' method ?

Collapse
 
aximuseng profile image
Dan Tappin

Can you customize the jbuilder templates?