Uploaded by rob

BaSAiR6 Ch3

advertisement
Build A SaaS App in Rails 6
Rob Race
© 2019 Rob Race
Contents
1.
Ruby on Rails and your First Application . . . . . . . . . .
1.1
Prerequisites . . . . . . . . . . . . . . . . . . . . . . . .
1.2
Rails Overview . . . . . . . . . . . . . . . . . . . . . .
1.2.1
Rails . . . . . . . . . . . . . . . . . . . . . .
1.2.2
Gems . . . . . . . . . . . . . . . . . . . . . .
1.3
A Sample App to Get You Started . . . . . . . . . . .
1.3.1
Rails Gem . . . . . . . . . . . . . . . . . . .
1.3.2
Installing Rails and Starting a New App .
1.3.3
Directory Structure . . . . . . . . . . . . .
1.3.4
Rails Server . . . . . . . . . . . . . . . . . .
1.3.5
Rails Conventions . . . . . . . . . . . . . .
1.3.6
Hello MVC . . . . . . . . . . . . . . . . . .
1.4
What did you learn in this chapter? . . . . . . . . . .
1.5
Exercises . . . . . . . . . . . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
. 1
. 1
. 2
. 2
. 2
. 3
. 3
. 4
. 4
. 5
. 7
. 8
. 10
. 10
2.
Testing and You . . . . . . . . . . . . . . . .
2.1
Why Test . . . . . . . . . . . . . . . .
2.2
Testing Frameworks . . . . . . . . .
2.3
Testing Types . . . . . . . . . . . . .
2.3.1
Model/Unit Tests . . . . .
2.3.2
Controller Tests . . . . .
2.3.3
View Tests . . . . . . . . .
2.3.4
System Tests . . . . . . .
2.4
Other Testing Tools . . . . . . . . . .
2.5
CI and You . . . . . . . . . . . . . . .
2.6
What did you learn in this chapter?
2.7
Exercises . . . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
11
11
11
13
13
14
15
16
17
18
18
18
3.
Starting your SaaS App . . . . . . . . . . .
3.1
rails new…for real this time . . . .
3.1.1
Install PG . . . . . . . . .
3.1.2
rails new with pg . . . .
3.1.3
Database Configuration
3.2
Git gud enuf . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
20
20
20
20
21
21
3. Starting your SaaS App
The time has come! We begin building a SaaS application right now. For this book, you are creating an
Agile/Scrum Daily Standup Tracking application. This sort of application will allow you to focus on
the basics, as well as some in-depth topics throughout the book. You will start a new Rails application.
Then continuing to build upon the application until you deploy the application live!
You have two approaches to building an application from this book. You can make the same
application as the book, step by step. Using that knowledge to build another application with your
business concept and logic. Otherwise, you can deviate immediately with your business idea and
use the same techniques
3.1 rails new…for real this time
3.1.1 Install PG
When building a production-ready application with Rails, the most popular database to use is
PostgreSQL. However, Rails via ActiveRecord does support a few databases out of the box. These
databases include PostgreSQL, MySQL, and SQLite3. Rails can support other databases through gems
and their ORM.
Since it is most likely you are using both PostgreSQL and MacOS(OSX), I recommend just
installing Postgres.app. Simply head to the postgres.app¹ site. Then download and follow the simple
instructions to have a PostgreSQL server and library installed on your box.
3.1.2 rails new with pg
By default, Rails uses SQLite3 for development databases. This database type is used to ease install
and development. However, since you are building an app that you intend to deploy, you are going
to use PostgreSQL in all of your environments. When initializing a new Rails app, it is quite easy to
change the default database type or other default settings.
rails new standup_app -T --database=postgresql
After a minute or two, Rails will have installed all the required files for the base application. Then
installed the base gems and ran a few other shell commands to get your application started.
¹https://postgresapp.com/
Starting your SaaS App
21
3.1.3 Database Configuration
If everything in the install went correctly, you would be able to use a Rails command to create the
database on your local PostgreSQL instance.
To be safe, take a look at the database configuration file and see what is in there. The database
configuration file config/database.yml comes with information about installing the pg gem. Along
with other option settings, you may change if you deviated at any point with your PostgreSQL setup.
Here is the relevant development connection information:
default: &default
adapter: postgresql
encoding: unicode
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
development:
<<: *default
database: standup_app_development
Here Rails is setting up a shared configuration between all environments setting the adapter type,
encoding, and connection pool settings. None of this is relevant to default usage of Rails, but you
may need to change this at some point in your application’s lifetime.
The development block specifies to use the default settings and then also use a database_name
of standup_app_development. The default naming for Rails databases is appname_environment.
Meaning your test database name would be standup_app_test and so on.
Now, let’s change our directory to that of the Rails app and create the database for Rails to use.
cd standup_app
rails db:create
This spot is a perfect place to pause, take a breather and celebrate that you have taken your first step
in creating your real Rails application. In the next section, we are going to go over the next critical
skill, source control.
3.2 Git gud enuf
The most shared and widespread way to manage your source code these days is Git. Git is a version
control system that allows you to take snapshots of your codebase. Also, it lets you branch your
codebase and traverse the git history if you need to move back to a previous state. Understanding
git may take a bit more explaining than this book could offer. I recommend reading the following
Starting your SaaS App
22
chapter from git-scm’s HTML ebook². If you are on MacOS(OSX), you can make sure you have the
git command line binary by making sure you have XCode’s Command line tools. This is done by
using the simple command:
xcode-select --install
On other systems, you can use the following link to install git Git Install Instructions³.
3.2.1 Git Commands
Now that you have git installed you can go over a few basic commands:
git init
Will create an empty Git repository or reinitialize an existing one
git clone
Will copy an existing repo(usually remote) into a new directory.
git status
Will show the working tree status. Meaning you can see which files/folders that have been added
or modified
git add
Will add files to your working index/branch to be committed
git rm
Will remove a file from index/branch that would have been staged for committal
git checkout
Allows you to switch between branches
²https://git-scm.com/book/en/v2/Getting-Started-Git-Basics
³https://git-scm.com/book/en/v2/Getting-Started-Installing-Git
Starting your SaaS App
23
git pull
Allows you pull and merge changes from a remote branch into your local branch.
git push
Allows you push committed changes from a remote branch into your local branch.
3.2.2 Git Usage
Now that you have a few commands under your belt, you can put them to actual use with your new
application.
git init
git add .
git commit -a -m "Initial commit"
Here you are initializing a new git repository in the current folder. Adding all files to be added by
passing a . (which means current directory in most command line systems). Then committing all
(-a flag) the changes, with a message(-m flag). Saying, you’d like to save the current progress of the
whole folder to git.
At this point, it is recommended you set up a remote repository host on a third party git hosting
system. There are three main places you can do this. GitHub⁴, Gitlab⁵ and BitBucket⁶.
GitHub has been the leading solution in the git hosting space for years and now offers free private
repository hosting. Bitbucket and Gitlab have been offering free plans for much longer, however.
You may be asking, “What is a private repository?”. When hosting your git repository, you have an
option to make it private. Which hides the repository to yourself and any collaborators you add.
A public repository, on the other hand, is indexed by search engines and visible to anyone on the
internet to see. For this book and the assumption you want to create an application whose source
code is not visible to all the internet, you want to host a private repository.
Github does offer a paid plan to have the ability to host unlimited private and public repositories.
There is another option called BitBucket. BitBucket is part of the Atlassian umbrella of products.
Atlassian houses other tools such as JIRA, Confluence, and others. BitBucket offers a free plan that
includes private repository hosting.
Additionally, Gitlab has a pretty robust internal CI/pipeline system that could replace CircleCi or
others in your pipeline, if you choose to set up such things.
Once you select a git host, you will be guided through account setup. Then, setting up a remote
repository, granting your system access to connect and given a command to run to connect your
local git repository to the remote repository. That command generally looks like:
⁴https://github.com
⁵https://gitlab.com
⁶https://bitbucket.com
Starting your SaaS App
24
git remote add origin git@github.com:username/repo_name.git
Lastly, you will run a push command to push the current git repository to your remote repository:
git push -u origin master
Perfect, your code base is now replicated on a remote git repository. This repository will be used as
a backup, a central place to pull the repository onto other machines you may have and can be used
in application deployments.
As we go further in the book we will cover git branches, git reset and other git commands as
they apply to your process.
3.3 Gemfile and preferred gems to start
A Gemfile is a file used by Bundler (Rails’ gem manager) to determine which gems to include in
the application. It also determines what version of a gem to use. This book is intended to take you
straight to the most useful and efficient way to create a SaaS application with Rails. Thus, you are
using a particular set of gems right out of the gate. The Gemfile in this book includes default gems,
remove a few gems and include gems that will be used in your application.
Gemfile
source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }
ruby '2.6.3'
gem
gem
gem
gem
gem
gem
gem
'rails', '~> 6.0.0'
'pg', '>= 0.18', '< 2.0'
'puma', '~> 3.11'
'sass-rails', '~> 5'
'webpacker', '~> 4.0'
'turbolinks', '~> 5'
'jbuilder', '~> 2.5'
gem 'redis', '~> 4.0'
gem 'local_time'
gem 'devise'
gem 'devise_invitable'
gem 'rolify'
Starting your SaaS App
25
gem 'cancancan'
gem 'immutable-struct'
gem 'sidekiq'
gem 'sinatra', require: nil
gem 'sidekiq-statistic'
gem 'gravatar_image_tag'
gem 'money-rails'
gem 'slack-notifier'
# Use Active Storage variant
# gem 'image_processing', '~> 1.2'
# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false
group :development, :test do
# Call 'byebug' anywhere in the code to stop execution and get a debugger console
gem 'byebug', platforms: [:mri]
gem 'rspec-rails'
gem 'rails-controller-testing'
gem 'capybara'
gem 'simplecov', :require => false, :group => :test
gem "factory_bot_rails"
end
group :development do
# Access an interactive console on exception pages or by calling 'console' anywher\
e in the code.
gem 'web-console', '>= 3.3.0'
gem 'listen', '>= 3.0.5', '< 3.2'
# Spring speeds up development by keeping your application running in the backgrou\
nd. Read more: https://github.com/rails/spring
gem 'spring'
gem 'spring-watcher-listen', '~> 2.0.0'
gem "letter_opener"
gem 'foreman'
end
Starting your SaaS App
26
gem 'rack-cors', :require => 'rack/cors'
You can run bundle install to install the gems specified in the Gemfile. However, if you run into a
version issue when running that, you may need to run bundle update to resolve version differences.
Most times Bundler tells you to run the update command if versions mismatch. Unfortunately,
Gemfiles and their dependencies can be fickle. The Gemfile I include is working at the point, and
time the book is being written. If you are copying code out of the included git repository, by copying
the Gemfile.lock, you are guaranteed to get the working versions from the complete application.
Now let’s go over some of the specific Gems added to our Gemfile:
• local_time⁷ - This is a gem that includes a view helper and javascript helper to display relative
time (i.e. ‘updated 2 minutes ago’)
• devise⁸ and devise_invitable⁹ - Devise is the authentication library most used in Rails for
the past few years. While there are some up and coming libraries to provide authentication
services the support around Devise and its ease of use make it great to move quickly on your
SaaS application. Devise Invitable is an add-on gem that will allow you to have an invitation
system to have users invite other users to your application.
• rolify¹⁰ and cancancan¹¹ - Rolify allows us to assign roles such as User or Admin then
CanCanCan allows us to take those roles and apply authorization policies.
• bootstrap-sass¹² - Allows us to use the Bootstrap HTML/CSS framework to build a useable
interface for the application quickly.
• Sidekiq¹³ - This gem allows us to run background jobs, backed by a Redis queue. Instead of
running tasks like sending emails on the main request loop. We can drop it into a Sidekiq queue
to run in the background.
• money-rails¹⁴ - This will help standardize around common issues found when calculating
money as well well providing a few template helpers.
• slack-notifier¹⁵ - Simple Slack API integration tool to quickly send a message to a Slack
Webhook. We’ll use this to send notifications to your own Slack when a new user signs up
or in other areas.
• rspec-rails¹⁶, factory_bot_rails¹⁷, simplecov¹⁸ - These gems include the testing functionality
discussed in Chapter 2.
• letter_opener¹⁹ - This gem will open mail in the browser instead of attempting to send mail
in the development environment.
⁷https://github.com/basecamp/local_time
⁸https://github.com/plataformatec/devise
⁹https://github.com/scambra/devise_invitable
¹⁰https://github.com/RolifyCommunity/rolify
¹¹https://github.com/CanCanCommunity/cancancan
¹²https://github.com/twbs/bootstrap-sass
¹³https://github.com/mperham/sidekiq
¹⁴https://github.com/RubyMoney/money-rails
¹⁵https://github.com/stevenosloan/slack-notifier
¹⁶https://github.com/rspec/rspec-rails
¹⁷https://github.com/thoughtbot/factory_bot_rails
¹⁸https://github.com/colszowka/simplecov
¹⁹https://github.com/ryanb/letter_opener
Starting your SaaS App
27
• rack-cors²⁰ - This gem will help set up CORS rules. This will be of use if accessing JSON
requests from domains that are not the current Rails’ app configured domain name.
This bulleted list is not an exhaustive list of all the gems installed that are used throughout your
application. However, it is a list of the gems that help you get your application built and ready for
customers.
Before moving on, let’s run the following command to do some post-bundler initializing:
rails generate rspec:install
3.4 Using Bootstrap in Rails
Bootstrap²¹ is an HTML/CSS/JS framework to make it quick and easy to build a responsive website.
Bootstrap 4 uses the flex-box browser position system and an optional grid layout system that breaks
the page into rows and 12 columns inside each row. This system now allows you to structure your
markup up in a consistent way.
Bootstrap, now being around for many years, has developed an ecosystem of themes as well. There
are both premium and free themes available. I like to use Inspinia as a premium theme or AdminLTE
as a free theme when building a SaaS application.
For the book, and due to its open source and free nature, we will use the AdminLTE theme. Feel
free to use any other themes. Though class names and markup structure may change unique to the
theme, you are using.
3.4.1 Quick overview of Flexbox and the Bootstrap Grid
First, let’s talk about Flexbox. Flexbox grew as a solution to the float based layouts that have been
popular for the better portion of the last decade. Using postion, float and display within CSS,
would allow a developer to build out a web page without using the pre-float-layout-era’s table
layouts.
Flexbox is a series of CSS attributes that allow the page to shink, grow and position to the dimensions
of a page or element.
Let’s dig into the Bootstrap 4’s grid system (specifically mentioning 4, as Bootstrap 3 did not use
Flexbox at all) in a little more detail before integrating Bootstrap into your application. By using
Flexbox, Bootstrap now allows you to build a gird without specifying column sizes that add up to
precisely 12 columns. When you create a row in the grid with <div class="row"></div> you can
fill that row with columns. In Bootstrap 4, one could create a 3 column layout with the following
HTML:
²⁰https://github.com/cyu/rack-cors
²¹http://getbootstrap.com
Starting your SaaS App
28
<div class="container">
<div class="row">
<div class="col">
One of three columns
</div>
<div class="col">
One of three columns
</div>
<div class="col">
One of three columns
</div>
</div>
</div>
However, Bootstrap allows you to set specific column sizes so that you can still target different sizing
for different viewport sizes. There are four sizes, each corresponding to the greatest pixel width.
Extra small (<576px) as .col. Small (≥576px) as .col-sm. Medium (≥768px) as .col-md. Large
(≥992px) as .col-lg. Extra Large (≥1200px) as .col-xl.
<!-- Stack the columns on mobile by
making one full-width and the other half-width -->
<div class="row">
<div class="col-12 col-md-8">One</div>
<div class="col-6 col-md-4">Two</div>
</div>
<!-- Columns start at 50% wide on mobile
and bump up to 33.3% wide on desktop -->
<div class="row">
<div class="col-6 col-md-4">One</div>
<div class="col-6 col-md-4">Two</div>
<div class="col-6 col-md-4">Three</div>
</div>
You can read the full layout guide here²².
3.4.2 Installing the Bootstrap Theme
To install the theme, we are going to be adding some packages, updating some configuration and
adding a few additional gems/libraries to make life developing easier.
²²https://getbootstrap.com/docs/4.3/layout/grid/
Starting your SaaS App
29
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
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
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
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
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
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.
²³https://github.com/nvm-sh/nvm
Starting your SaaS App
30
rails webpacker:install
Alright, we are getting close to being done with setup! However, we should digress a bit in regards
to Webpacker.
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.
3.4.2.1 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'
...
Once added, you can add your Procfile to the root of app’s folder structure. Meaning the same level
as your Gemfile.
Starting your SaaS App
31
Procfile
web: bundle exec puma -t 5:5 -p ${PORT:-3000} -e ${RACK_ENV:-development}
You may notice that instead of rails s, you see bundle exec puma. This change is due to how you
would want to rub 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
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 that is utilized just for development.
Procfile.dev
web: bin/rails s -p ${PORT:-3000}
webpacker: ./bin/webpack-dev-server
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:
foreman start -f Procfile.dev
We’re getting closer! Now if you visit http://localhost:5000, you should see the output in the terminal
from your request. However, we’re still on port 5000. This problem is easy to fix, supply the command
line with the environment variable used in the command to choose the port.
PORT=3000 foreman start -f Procfile.dev
Now, you can quickly start everything up and get it running in one terminal tab.
3.4.2.2 Final Webpacker Setup
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.
Starting your SaaS App
32
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
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.
3.4.2.3 The Application Pack
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/packs/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.
app/javascript/packs/application.js
// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application log\
ic in
// a relevant structure within app/javascript and only use these pack files to refer\
ence
// that code so it'll be compiled.
require("@rails/ujs").start()
require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
var jQuery = require("jquery")
Starting your SaaS App
33
// 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 refe\
rence
// 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)
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.
3.4.2.4 The Application Stylesheet
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.
Starting your SaaS App
34
app/assets/stylesheets/application.css
/*
* This is a manifest file that'll be compiled into application.css, which will incl\
ude all the files
* listed below.
*
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugi\
n'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 a\
ny other CSS/SCSS
* files in this directory. Styles in this file should be added after the last requi\
re_* 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';
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.
3.4.3 First Controller, Views and Tests
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
Starting your SaaS App
35
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
Running via Spring preloader in process 98027
create app/controllers/activity_controller.rb
route get 'activity/mine' get 'activity/feed'
invoke erb
create
app/views/activity
create
app/views/activity/mine.html.erb
create
app/views/activity/feed.html.erb
invoke rspec
create
spec/controllers/activity_controller_spec.rb
create
spec/views/activity
create
spec/views/activity/mine.html.erb_spec.rb
create
spec/views/activity/feed.html.erb_spec.rb
invoke helper
create
app/helpers/activity_helper.rb
invoke
rspec
create
spec/helpers/activity_helper_spec.rb
invoke assets
invoke
scss
create
app/assets/stylesheets/activity.scss
I included the output of the command this time to show how much Rails, and the added gems
have created and stubbed out for us. 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:
app/config/routes.rb
Rails.application.routes.draw do
get 'activity/mine'
get 'activity/feed'
root to: 'activity#mine'
end
As you saw, there were a few test files automatically created by the generator. Let’s run the RSpec
command to see what happens now:
Starting your SaaS App
36
rspec
...
Finished in 0.03571 seconds (files took 5.8 seconds to load)
5 examples, 2 failures, 3 pending
Failed examples:
rspec ./spec/controllers/activity_controller_spec.rb:6 # ActivityController GET #min\
e returns http success
rspec ./spec/controllers/activity_controller_spec.rb:13 # ActivityController GET #fe\
ed returns http success
I truncated the output a fair bit as some errors and deprecations are consuming quite a few lines.
However, as you see, there are 2 failed specs and 3 pending specs, which are lines of RSpec TODOs.
The errors are stemming from the rspec-rails package not being completely compatible with Rails
6 (at the time of writing). The fix here is to use the latest master branch version of rspec-rails.
Head to the Gemfile and replace
gem 'rspec-rails'
with
gem 'rspec-rails', :github => 'rspec/rspec-rails', :branch => '4-0-dev'
and run bundle install. This change tells Bundler to grab the gem from Github instead of the
usual RubyGems repository and get the latest version from the 4-0-dev branch. This change fixes
the controller specs and allows them to work as intended when they were generated.
It also created a helper test file with a pending stub, which we delete. Lastly, it created pending test
stubs for each of our view files. This pause would be a good time to write some simple tests there to
get some practice.
First, let’s go ahead and edit the feed spec.
Starting your SaaS App
37
spec/views/activity/feed.html.erb_spec.rb
require 'rails_helper'
RSpec.describe "activity/feed.html.erb", type: :view do
it "renders the word feed" do
render :template => "activity/feed.html.erb"
expect(rendered).to match(/feed/)
end
end
Now, if we run the spec you see that both the previous, pending helper note is no longer listed and
the feed spec is marked as working:
rspec
...*
Pending: (Failures listed here are expected and do not affect your suites status)
1) activity/mine.html.erb add some examples to (or delete) standup_app/spec/views/\
activity/mine.html.erb_spec.rb
# Not yet implemented
# ./spec/views/activity/mine.html.erb_spec.rb:4
Finished in 0.02383 seconds (files took 2.98 seconds to load)
4 examples, 0 failures, 1 pending
Finally, we can clean up the last pending spec, for the mine view in the same way:
spec/views/activity/mine.html.erb_spec.rb
require 'rails_helper'
RSpec.describe "activity/mine.html.erb", type: :view do
it "renders the word mine" do
render :template => "activity/mine.html.erb"
expect(rendered).to match(/mine/)
end
end
Run the rspec command one more time:
Starting your SaaS App
38
rspec
....
Finished in 0.02457 seconds (files took 3.08 seconds to load)
4 examples, 0 failures
As you see, we are good to go. There are four passing specs and no pending or unneeded specs.
Now that you have some testing out of the way, let’s start to set up your layout.
3.4.4 Application layout
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>Standup App</title>
<%= csrf_meta_tags %>
<%= csp_meta_tag %>
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'r\
eload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<script src="https://kit.fontawesome.com/6d90f39943.js"></script>
</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>
Starting your SaaS App
39
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 <div class="content-wrapper"> 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="S\
earch" aria-label="Search">
<div class="input-group-append">
<button class="btn btn-navbar" type="submit">
Starting your SaaS App
40
<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-si\
ze-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-sta\
r"></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 Ho\
urs 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-si\
ze-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\
Starting your SaaS App
41
"></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 Ho\
urs 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-si\
ze-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-st\
ar"></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 Ho\
urs 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
Starting your SaaS App
42
<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: aut\
o; 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">
Starting your SaaS App
43
<img src="http://placehold.it/160x160" class="img-circle elevation-2" alt="U\
ser 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>
Starting your SaaS App
44
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>
3.4.5 Turbolinks
There is one more thing to touch on before ending this chapter and is important enough to have
its section before getting into some more in-depth topics. Turbolinks, created by Basecamp(which is
partly owned by the creator of Ruby on Rails) is a default Rails javascript library whose main job is
to make your application faster to navigate.
Turbolinks achieves this by hijacking the links within a page and only replace the markup that
changes based on the route/controller/action. The reason why this can be important is that it lets
you continue to build a Rails application with few changes instead of learning and to implement
the “Front End” of the application in an entirely different javascript framework or library(EmberJS,
ReactJS, VueJS, etc.).
The caveat to not changing any code is that Turbolinks creates listeners to specific Turbolink
javascript events. At times, this causes an issue with other library’s standard jQuery ready events.
However, the fix at most times is to make sure you are binding the libraries listeners within a
Turbolink’s version of the DOM being ready (which is document.ready if you are already jQuery
inclined)
3.4.6 One more thing…
With all the progress you have made in this chapter, it would be an excellent time to commit your
changes. Use git and push the changes to your remote repository. Here are the commands you could
use if you haven’t been committing more frequently already:
Starting your SaaS App
45
git add .
git commit -am "Added an application layout"
git push
3.5 What did you learn in this chapter?
• You learned how to start a new Rails app with non-default settings. Including starting without
including the default test framework and using PostgreSQL as a database.
• You learned some basics about git and remote git repositories.
• You learned about some of the more useful gems when building a purposeful Rails application
• You generated a controller, actions, and views from the command line.
• You wrote helpful view tests.
• You implemented Bootstrap and a theme to your application. Which included markup,
javascript, and stylesheets.
• You learned a basic amount about Turbolinks.
3.6 Exercises
Exercise answers can be found with any package or bundle purchases of Build A SaaS App in Ruby
on Rails 6²⁴!
1. While it may not always be the most preferred approach, you can use rails to serve static pages.
Create a Controller, Route and View template for a static page that will display some text on
how to reach you for support.
2. While the Rails app you will be creating in this book had only one change in the rails new
command, it would benefit you to look at the help text for the commands you ran in this
chapter. Run rails new --help and rails generate controller --help.
²⁴https://buildasaasappinrails.com
Download