DEV Community

Cover image for Integrating Bootstrap 4 into Rails 6
Rob Race
Rob Race

Posted on • Edited on • Originally published at robrace.dev

Integrating Bootstrap 4 into Rails 6

To install Bootstrap 4 and a theme, we are going to be adding some packages, updating some configuration and adding a few additional gems/libraries to make life developing easier. This is going to assume you have already started an app with rails new app_name


Note: This tutorial is an excerpt from a chapter in my upcoming book Build A SaaS App in Rails 6. The book guides you from humble beginnings by deploying an app to production. The book is now in pre-sale, and you can grab a free chapter right now!


The first thing to do is to add another version manager! Since we are using Yarn/NPM/Node, it is a good idea to manage the version of Node a particular application or directory will be using. To do so, we will use NVM, and you can install the NVM with the following line. If you are one to look at the github repo or the install scripts, you can here.

curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.34.0/install.sh | bash
Enter fullscreen mode Exit fullscreen mode

That line grabs the install script from Github and runs it as Bash.

Now the current LTS (Long Term Stable) version of Node is version 10. LTS versions in the OSS (Open Source) communities is the version in which you can rely on long term stability. To install Node use the following command:

nvm install 10
Enter fullscreen mode Exit fullscreen mode

This line installs the latest updated version of version 10. Meaning, as small updates and bugfixes are released to the version 10 pipeline, they bump the version numbers under 10 (i.e., 10.3.4 -> 10.3.5). Once installed, you can tell your terminal window/pane to use node 10 with the following command:

nvm use 10
Enter fullscreen mode Exit fullscreen mode

When you created your new app, Rails should have made sure brew and yarn were added. Thus, you should now be able to install AdminLTE, Bootstrap, and a few other dependencies needed to get AdminLTE up and running in your application.

First, to install a version of AdminLTE that is compatible with Bootstrap 4, we will need to install through Yarn on a specific Github branch.

yarn add https://github.com/ColorlibHQ/AdminLTE#v3-dev
Enter fullscreen mode Exit fullscreen mode

This command is saying, install AdminLTE to the remote Github repo ColorlibHQ/AdminLTE and use the v3-dev. Next, we also install the packages for Bootstrap, jQuery and Popper.js to make sure they are available to the Rails application:

yarn add bootstrap jquery popper.js
Enter fullscreen mode Exit fullscreen mode

That should take care of making sure all of the packages we need for the theme are there, but it seems as though Rails does not entirely install everything needed for Webpacker to get up and running. To remedy this issue, let’s run the Webpacker install.

rails webpacker:install
Enter fullscreen mode Exit fullscreen mode

Alright, we are getting close to being done with setup! However, we should digress a bit in regards to Webpacker.

Webpacker is the Ruby (Rails mostly) wrapper gem around the Javascript tool, Webpack. Webpack is a tool that allows you to pre-process, bundle and use ES6 like syntax inside Javascript. Webpacker then takes all of those features and starts to merge the ES6 Javascript world with the Asset pipeline for CSS and other more static assets.

Webpacker introduces a concept of "packs" which are ES6 syntax Javascript files that you can then include in any template with a pack_tag helper. Think of packs as entry points of javascript files into specific spots in a Rails app.

The most basic pattern is the /app/javascript/application.js pack file, which is then included in the applications’ layout file by default.

As your application grows, using packs to specify specific libraries and functionality within specific templates or sections of your application helps make sure your Javascript stays modular and compartmentalized.

Running A Webpack Dev Server

If you have used Heroku or other hosting vendors you may see reference to Procfile. The simplest definition of a Procfile is that it allows you to specify a list of different process types and let another system(Heroku, Foreman gem, etc.) handle startup and shutdown. Allow all of that to be dealt with one line and one terminal session/tab/pane.

When developing locally, the easiest step to take is to use the gem Foreman. To add this gem, add it to the development group in your Gemfile and bundle install:

...
group :development do
  gem 'foreman'
...
Enter fullscreen mode Exit fullscreen mode

Once added, you can add your Procfile to the root of app’s folder structure. Meaning the same level as your Gemfile.

web: bundle exec puma -t 5:5 -p ${PORT:-3000} -e ${RACK_ENV:-development}
Enter fullscreen mode Exit fullscreen mode

You may notice that instead of rails s, you see bundle exec puma. This change is due to how you would want to run Puma directly in most environments.

If you were to start your environment with Foreman and this procfile, you would use the following on the command line:

foreman start
Enter fullscreen mode Exit fullscreen mode

That command starts up a process for each line in your Procfile. Thus, in this case, a process for Puma, to run the Rails server. However, if you visit your instance of the app, first you may notice it is now at http://localhost:5000, and it is also not outputting development logs.

So, how can we make development use rails s to keep the fancy logs? We can create a secondary Procfile.dev that is utilized just for development.

web: bin/rails s -p ${PORT:-3000}
webpacker: ./bin/webpack-dev-server
Enter fullscreen mode Exit fullscreen mode

What is webpack-dev-server?

Very simply, it is a server that runs in the background that allows you to develop your Javascript, hand automatic reloads and making sure packs are served to you your development Rails server.

To get this Procfile running instead, you simply just need to add a field to your command line syntax:

PORT=3000 foreman start -f Procfile.dev
Enter fullscreen mode Exit fullscreen mode

Ok, we’re getting so close to getting everything up and running. We’re down to adding just a few additions to the Webpack javascript configuration.

// config/webpack/environment.js
const { environment } = require('@rails/webpacker')
const webpack = require('webpack')
// Add an additional plugin of your choosing : ProvidePlugin
environment.plugins.prepend('Provide', new webpack.ProvidePlugin({
    $: 'jquery',
    JQuery: 'jquery',
    jquery: 'jquery',
    'window.Tether': "tether",
    Popper: ['popper.js', 'default'], // for Bootstrap 4
  })
)
module.exports = environment
Enter fullscreen mode Exit fullscreen mode

One of the additions are the 8 lines of plugin code that is used to make sure jQuery, Popper.js and Tether are available throughout the bundled javascript.

When adding Javascript to a Rails application, there are javascript libraries, setup or variables you want throughout the application. In the world of Rails 6 and Webpacker, these items go in the Application Pack, located at /app/javascript/application.js. The good news is that this file a bit of content is created when you used the Rails command to create a new Rails app. Let’s go over the additions to get AdminLTE’s needs fulfilled.

/ This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
var jQuery = require("jquery")
// import jQuery from "jquery";
global.$ = global.jQuery = jQuery;
window.$ = window.jQuery = jQuery;
require('bootstrap');
require('admin-lte')
// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below.
//
// const images = require.context('../images', true)
// const imagePath = (name) => images(name, true)
Enter fullscreen mode Exit fullscreen mode

The commented code and the first four lines of requires are added by default. These are libraries or packages that Rails needs for most applications. The new lines are adding jQuery and variables that other packages or libraries may expect to exist. Lastly, bootstrap and admin-lte are required to make available to the entire Rails application.

Why require and not import?

If you have done any recent javascript development, you may question why we are using require and not import. The answer here is rather simple. Based on the packages we are using and adding in, they expect other’s to have loaded first. Most specifically, admin-lte expects the global variable jQuery to exist when it loads. Using require guarantees code is executed in the order of the file, whereas import usually is pre-processed and executed out of order.

Lastly, we need to add some imports to the bottom of the automatically created Application Stylesheet. If you have done any Rails development in the last few years, you may have interacted with the Asset Pipeline, which is the way Rails handled the inclusion of javascript, stylesheets and any other static assets.

app/assets/stylesheets/application.css

*
 * This is a manifest file that'll be compiled into application.css, which will include all the files
 * listed below.
 *
 * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
 * vendor/assets/stylesheets directory can be referenced here using a relative path.
 *
 * You're free to add application-wide styles to this file and they'll appear at the bottom of the
 * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
 * files in this directory. Styles in this file should be added after the last require_* statement.
 * It is generally better to create a new file per style scope.
 *
 *= require_tree .
 *= require_self
 */
@import url("https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,600,700&lang=en");
@import 'bootstrap';
@import 'admin-lte/dist/css/adminlte';
Enter fullscreen mode Exit fullscreen mode

In CSS, you can include other stylesheets or fonts by using the @import syntax. In this stylesheet above, we are adding a font from Google Fonts that are referenced in AdminLTE’s stylesheets. Next, we include stylesheets for Bootstrap, AdminLTE and Font Awesome (the icons referenced by AdminLTE).

In the following section, we are going to add some Controllers and Templates to added the necessary markup to structure the application.

Before we begin implementing the layout, we need to apply a route and controller to bypass the default welcome page. We also will be using this controller in the application as we go along.

Let’s also pause to mention that Rails comes with built-in command line generators to generate controllers, models, mailers. As well as generating database migration file or even generating a scaffold of all of those combined. In our case, let’s focus on the controller generator. The general format for this generator is:

rails generate controller Activity mine feed
Enter fullscreen mode Exit fullscreen mode

It created a controller with empty methods for the actions we included in the command line. It created template files, test files, and other auxiliary files.

Let’s make one quick edit to the route file to define a default route, adding “root to: ‘activity#mine’” to the bottom of the route file, as follows:

Rails.application.routes.draw do
  get activity/mine
  get activity/feed
  root to: activity#mine’
end
Enter fullscreen mode Exit fullscreen mode

As we had set up the theme package, application javascript file and application stylesheet file earlier in the chapter, we can get into writing the markup needed to implement this theme rather quickly.

First, we can modify the main layout file to include the necessary elements and classes to wrap the content that will be provided by routed templates.

app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>Some App</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>
    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
    <script src="https://kit.fontawesome.com/6d90f39943.js"></script> <-- Make your own FontAwesome Kit on their site -->
  </head>
<body class="sidebar-mini">
    <div class="wrapper">
      <%= render partial: "layouts/header" %>
      <%= render partial: "layouts/sidebar" %>
      <div class="content-wrapper">
        <%= yield %>
      </div>
      <%= render partial: "layouts/footer" %>
    </div>
  </body>
</html>
Enter fullscreen mode Exit fullscreen mode

The details of this file are not all pertinent to the addition of the Bootstrap theme, so we’ll only touch on those that matter. First, a class is added to the body element, body class=”sidebar-mini. Next, <div class="wrapper"> and “

” are added as wrapping elements to the first the page and then the inner content. You may notice there is some included Ruby code in there as well. Those render partial: calls allow us to add markup from other template files, in that position. In this case, it adds various larger parts of the application template such as the header, sidebar or footer. Plus, it keeps the layout to only the most necessary foundational elements to build the page structure. Then, <%= yield %> which is the Rails’ template method for adding the template from the controller and action called.

Since we are using partial templates, we go ahead and create the header file next.

One thing to note here is the use of the underscore in the template file’s name. In Rails, partial template files are designated with an underscore at the beginning of the file name. What are partials? Partials are simply a way to break down templates into smaller chunks. This approach comes into far greater play in situations such as looping over a collection of items. There, you would make the template chunk to display item information a partial. For example, the header template is titled _header.erb.

app/views/layout/_header.erb

<nav class="main-header navbar navbar-expand navbar-white navbar-light border-bottom">
    <!-- Left navbar links -->
    <ul class="navbar-nav">
      <li class="nav-item">
        <a class="nav-link" data-widget="pushmenu" href="#"><i class="fas fa-bars"></i></a>
      </li>
      <li class="nav-item d-none d-sm-inline-block">
        <a href="index3.html" class="nav-link">Home</a>
      </li>
      <li class="nav-item d-none d-sm-inline-block">
        <a href="#" class="nav-link">Contact</a>
      </li>
    </ul>
<!-- SEARCH FORM -->
    <form class="form-inline ml-3">
      <div class="input-group input-group-sm">
        <input class="form-control form-control-navbar" type="search" placeholder="Search" aria-label="Search">
        <div class="input-group-append">
          <button class="btn btn-navbar" type="submit">
            <i class="fas fa-search"></i>
          </button>
        </div>
      </div>
    </form>
<!-- Right navbar links -->
    <ul class="navbar-nav ml-auto">
      <!-- Messages Dropdown Menu -->
      <li class="nav-item dropdown">
        <a class="nav-link" data-toggle="dropdown" href="#">
          <i class="far fa-comments"></i>
          <span class="badge badge-danger navbar-badge">3</span>
        </a>
        <div class="dropdown-menu dropdown-menu-lg dropdown-menu-right">
          <a href="#" class="dropdown-item">
            <!-- Message Start -->
            <div class="media">
              <img src="http://placehold.it/128x128" alt="User Avatar" class="img-size-50 mr-3 img-circle">
              <div class="media-body">
                <h3 class="dropdown-item-title">
                  Brad Diesel
                  <span class="float-right text-sm text-danger"><i class="fas fa-star"></i></span>
                </h3>
                <p class="text-sm">Call me whenever you can...</p>
                <p class="text-sm text-muted"><i class="far fa-clock mr-1"></i> 4 Hours Ago</p>
              </div>
            </div>
            <!-- Message End -->
          </a>
          <div class="dropdown-divider"></div>
          <a href="#" class="dropdown-item">
            <!-- Message Start -->
            <div class="media">
              <img src="http://placehold.it/128x128" alt="User Avatar" class="img-size-50 img-circle mr-3">
              <div class="media-body">
                <h3 class="dropdown-item-title">
                  John Pierce
                  <span class="float-right text-sm text-muted"><i class="fas fa-star"></i></span>
                </h3>
                <p class="text-sm">I got your message bro</p>
                <p class="text-sm text-muted"><i class="far fa-clock mr-1"></i> 4 Hours Ago</p>
              </div>
            </div>
            <!-- Message End -->
          </a>
          <div class="dropdown-divider"></div>
          <a href="#" class="dropdown-item">
            <!-- Message Start -->
            <div class="media">
              <img src="http://placehold.it/128x128" alt="User Avatar" class="img-size-50 img-circle mr-3">
              <div class="media-body">
                <h3 class="dropdown-item-title">
                  Nora Silvester
                  <span class="float-right text-sm text-warning"><i class="fas fa-star"></i></span>
                </h3>
                <p class="text-sm">The subject goes here</p>
                <p class="text-sm text-muted"><i class="far fa-clock mr-1"></i> 4 Hours Ago</p>
              </div>
            </div>
            <!-- Message End -->
          </a>
          <div class="dropdown-divider"></div>
          <a href="#" class="dropdown-item dropdown-footer">See All Messages</a>
        </div>
      </li>
      <!-- Notifications Dropdown Menu -->
      <li class="nav-item dropdown">
        <a class="nav-link" data-toggle="dropdown" href="#">
          <i class="far fa-bell"></i>
          <span class="badge badge-warning navbar-badge">15</span>
        </a>
        <div class="dropdown-menu dropdown-menu-lg dropdown-menu-right">
          <span class="dropdown-item dropdown-header">15 Notifications</span>
          <div class="dropdown-divider"></div>
          <a href="#" class="dropdown-item">
            <i class="fas fa-envelope mr-2"></i> 4 new messages
            <span class="float-right text-muted text-sm">3 mins</span>
          </a>
          <div class="dropdown-divider"></div>
          <a href="#" class="dropdown-item">
            <i class="fas fa-users mr-2"></i> 8 friend requests
            <span class="float-right text-muted text-sm">12 hours</span>
          </a>
          <div class="dropdown-divider"></div>
          <a href="#" class="dropdown-item">
            <i class="fas fa-file mr-2"></i> 3 new reports
            <span class="float-right text-muted text-sm">2 days</span>
          </a>
          <div class="dropdown-divider"></div>
          <a href="#" class="dropdown-item dropdown-footer">See All Notifications</a>
        </div>
      </li>
      <li class="nav-item">
        <a class="nav-link" data-widget="control-sidebar" data-slide="true" href="#"><i class="fas fa-th-large"></i></a>
      </li>
    </ul>
  </nav>

This template contains the structure to have the app title/logo, dropdowns and any additional links you may need for navigation. To get the app template structure setup, we can leave in some of the demo data and elements to show how the dropdowns would look and work.

The next partial to create is the _sidebar template:

app/views/layouts/_sidebar.erb

<aside class="main-sidebar sidebar-dark-primary elevation-4">
    <!-- Brand Logo -->
  <a href="index3.html" class="brand-link">
    <span class="brand-text font-weight-light">Standup App</span>
  </a>
<!-- Sidebar -->
  <div class="slimScrollDiv" style="position: relative; overflow: hidden; width: auto; height: 652px;"><div class="sidebar" style="min-height: 652px; overflow: hidden; width: auto; height: 652px;">
    <!-- Sidebar user panel (optional) -->
    <div class="user-panel mt-3 pb-3 mb-3 d-flex">
      <div class="image">
        <img src="http://placehold.it/160x160" class="img-circle elevation-2" alt="User Image">
      </div>
      <div class="info">
        <a href="#" class="d-block">User Name</a>
      </div>
    </div>
<!-- Sidebar Menu -->
    <nav class="mt-2">
      <ul class="nav nav-pills nav-sidebar flex-column" data-widget="treeview" role="menu" data-accordion="false">
        <!-- Add icons to the links using the .nav-icon class
             with font-awesome or any other icon font library -->
        <li class="nav-item has-treeview menu-open">
          <a href="#" class="nav-link active">
            <i class="nav-icon fas fa-tachometer-alt"></i>
            <p>
              Activity
              <i class="right fas fa-angle-left"></i>
            </p>
          </a>
          <ul class="nav nav-treeview">
            <li class="nav-item">
              <a href="./index.html" class="nav-link active">
                <i class="far fa-circle nav-icon"></i>
                <p>Mine</p>
              </a>
            </li>
            <li class="nav-item">
              <a href="./index2.html" class="nav-link">
                <i class="far fa-circle nav-icon"></i>
                <p>Feed</p>
              </a>
            </li>
          </ul>
        </li>
      </ul>
    </nav>
    <!-- /.sidebar-menu -->
  </div>
  <!-- /.sidebar -->
</aside>
As you see, the sidebar partial is a bit smaller and more straightforward. It contains a little bit of user information and links to other parts of the application that we change later.

The last major piece of the layout is the footer. It is simple, and it is short. Again, it is another partial. Here is the ERB markup:

app/views/layouts/_foooter.erb
<footer class="main-footer">
  <strong>Copyright © 2019 Your Stuff.</strong>
  All rights reserved.
</footer>
Now, to have all of this show up on a page, you can edit the root controller action’s template to use the theme’s div classes

app/views/activity/mine.html.erb
<div class="content-header">
  <div class="container-fluid">
    <div class="row mb-2">
      <div class="col-sm-6">
        <h1 class="m-0 text-dark">Some Page</h1>
      </div>
    </div>
  </div>
</div>
<section class="content">
  <div class="container-fluid">
    <div class="row">
      <div class="col-lg-12">
        <div class="card">
          <div class="card-body">
            <span>Stuff</span>
          </div>
        </div>
      </div>
    </div>
  </div>
</section>

That’s about it to get set up with Bootstrap 4 and Rails 6.

Top comments (4)

Collapse
 
patrixr profile image
Patrick R

Question a bit out of the blue, but these days is there still value in having backend rendering and the UI integrated with rails ? As opposed to a bundled app which just gets delivered statically ?

I've been doing the latter for a while now (react, vue, etc), just curious to hear your thoughts

Collapse
 
ark profile image
Ark Shraier • Edited

I'm not the author of this post, but still writing server side rendered UI in Rails.

Maybe there're not all points, but some major ones in my humble opinion:

  1. In Rails writing server-side UI is out-of-the-box so it cost you almost no time. Also Turbolinks can make it behave/feel like SPA.

  2. You need server-side rendered pages for the SEO (it's changing now)

  3. No overhead with authorization (think JWT)

  4. You can sleep well not chasing for super-fast changing JS frameworks world :)

Collapse
 
tomk32 profile image
Thomas R. Koll

SEO is the primary reason for me to stay away from SPA and stick to Rails for a longer time. My landing pages and SEO content are as simple as can be to keep the chance of them to fail for some reason as low as possible.

Collapse
 
gathuku profile image
Moses Gathuku

Hi @rob_race , I have a question regarding this , I added admin lte in my rails 6 app but am unable to implement plugins Like the datatables, datepicker and graphs. Could you please help.