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