Category Archives: Ruby on Rails

How to Dockerize a Sinatra application

Why we’re doing this

Docker is difficult

In my experience, Dockerizing a Rails application for the first time is pretty hard. Actually, doing anything with Docker seems pretty hard. The documentation isn’t that good. Clear examples are hard to find.

Dockerizing Rails is too ambitious as a first goal

Whenever I do anything for the first time, I want to do the simplest, easiest possible version of that thing before I try anything more complicated. I also never want to try to learn more than one thing at once.

If I try to Dockerize a Rails application without any prior Docker experience, then I’m trying to learn the particulars of Dockerizing a Rails application while also learning the general principles of Docker at the same time. This isn’t a great way to go.

Dockerizing a Sinatra application gives us practice

Dockerizing a Sinatra application lets us learn some of the principles of Docker, and lets us get a small Docker win under our belt, without having to confront all the complications of Dockerizing a Rails application. (Sinatra is a very simple Ruby web application framework.)

After we Dockerize our Sinatra application we’ll have a little more confidence and a little more understanding than we did before. This confidence and understanding will be useful when we go to try to Dockerize a Rails application (which will be a future post).

By the way, if you’ve never worked with Sinatra before, don’t worry. No prior Sinatra experience is necessary.

What we’re going to do

Here’s what we’re going to do:

  1. Create a Sinatra application
  2. Run the Sinatra application to make sure it works
  3. Dockerize the Sinatra application
  4. Run the Sinatra application using Docker
  5. Shotgun a beer in celebration (optional)

I’m assuming you’re on a Mac and that you already have Docker installed. If you don’t want to copy/paste everything, I have a repo of all the files here.

(Side note: I must give credit to Marko Anastasov’s Dockerize a Sinatra Microservice post, from which this post draws heavily.)

Let’s get started.

Creating the Sinatra application

Our Sinatra “application” will have just one file. The application will have just one endpoint. Create a file called hello.rb with the following content.

# hello.rb

require 'sinatra'

get '/' do
  'It works!'
end

We’ll also need to create a Gemfile that says Sinatra is a dependency.

# Gemfile

source 'https://rubygems.org'

gem 'sinatra'

Lastly for the Sinatra application, we’ll need to add the rackup file, config.ru.

# config.ru

require './hello'

run Sinatra::Application

After we run bundle install to install the Sinatra gem, we can run the Sinatra application by running ruby hello.rb.

$ bundle install
$ ruby hello.rb

Sinatra apps run on port 4567 by default, so let’s open up http://localhost:4567 in a browser.

$ open http://localhost:4567

If everything works properly, you should see the following.

Dockerizing the Sinatra application

Dockerizing the Sinatra application will involve two steps. First, we’ll create a Dockerfile will tells Docker how to package up the application. Next we’ll use our Dockerfile to build a Docker image of our Sinatra application.

Creating the Dockerfile

Here’s what our Dockerfile looks like. You can put this file right at the root of the project alongside the Sinatra application files.

# Dockerfile

FROM ruby:2.7.4

WORKDIR /code
COPY . /code
RUN bundle install

EXPOSE 4567

CMD ["bundle", "exec", "rackup", "--host", "0.0.0.0", "-p", "4567"]

Since it might not be clear what each part of this file does, here’s an annotated version.

# Dockerfile

# Include the Ruby base image (https://hub.docker.com/_/ruby)
# in the image for this application, version 2.7.4.
FROM ruby:2.7.4

# Put all this application's files in a directory called /code.
# This directory name is arbitrary and could be anything.
WORKDIR /code
COPY . /code

# Run this command. RUN can be used to run anything. In our
# case we're using it to install our dependencies.
RUN bundle install

# Tell Docker to listen on port 4567.
EXPOSE 4567

# Tell Docker that when we run "docker run", we want it to
# run the following command:
# $ bundle exec rackup --host 0.0.0.0 -p 4567.
CMD ["bundle", "exec", "rackup", "--host", "0.0.0.0", "-p", "4567"]

Building the Docker image

All we need to do to build the Docker image is to run the following command.

I’m choosing to tag this image as hello, although that’s an arbitrary choice that doesn’t connect with anything inside our Sinatra application. We could have tagged it with anything.

The . part of the command tells docker build that we’re targeting the current directory. In order to work, this command needs to be run at the project root.

$ docker build --tag hello .

Once the docker build command successfully completes, you should be able to run docker images and see the hello image listed.

Running the Docker image

To run the Docker image, we’ll run docker run. The -p 4567:4567 portion says “take whatever’s on port 4567 on the container and expose it on port 4567 on the host machine”.

$ docker run -p 4567:4567 hello

If we visit http://localhost:4567, we should see the Sinatra application being served.

$ open http://localhost:4567

Conclusion

Congratulations. You now have a Dockerized Ruby application!

With this experience behind you, you’ll be better equipped to Dockerize a Rails application the next time you try to take on that task.

Why I don’t use Shoulda matchers

One of the testing questions I commonly get is about Shoulda matchers. People ask if I use Shoulda matchers and if Shoulda matchers are a good idea.

I’ll share my thoughts on this. First I’ll explain what Shoulda is, then I’ll explain why I don’t use it.

What Shoulda is

If you’re unfamiliar with Shoulda matchers, the premise, from the GitHub description, is: “Shoulda Matchers provides RSpec- and Minitest-compatible one-liners to test common Rails functionality that, if written by hand, would be much longer, more complex, and error-prone.”

A few examples of specific Shoulda matchers are validates_presence_of (expects that a model attribute has a presence validator), have_many (expects that a has_many association exists), and redirect_to (expects that a redirection takes place).

I like the idea of a library that can clean up a lot of my repetitive test code. Unfortunately, Shoulda matchers only apply to the kinds of tests I would never write.

Test behavior, not implementation

To me it doesn’t make much sense to, for example, write a test that only checks for the presence of an Active Record association and doesn’t do anything else.

If I have an association, presumably that association exists in order to enable some piece of behavior, or else it would be pointless for the association to exist. For example, if a User class has_many :posts, then that association only makes sense if there’s some Post-related behavior.

So there are two possibilities in light of testing that the User class has_many :posts. One is that I write a test for both the association itself and the behavior enabled by the association, in which case the test for the association is redundant and adds no value. The other possibility is that I write a test only for the post association, but not for the post behavior, which wouldn’t make much sense because why wouldn’t I write a test for the post behavior?

To me it only makes sense in this example to write tests for the post behavior and write no tests directly for the association. The logic of this decision can be proved by imagining what would happen if the has_many :posts line were removed. Any tests for the post behavior would start failing because the behavior would be broken without the association line present.

Takeaway

Don’t test low-level implementations. It’s pointless. Test behavior instead.

Since Shoulda is only good for testing low-level implementations, I don’t recommend using it.

How I set up Factory Bot on a fresh Rails project

A reader of mine recently asked me how I set up Factory Bot for a new Rails project.

There are four steps I go through to set up Factory Bot.

  1. Install the factory_bot_rails gem
  2. Set up one or more factory definitions
  3. Install Faker
  4. Add the Factory Bot syntax methods to my rails_helper.rb file

Following are the details for each step.

Install the factory_bot_rails gem

The first thing I do is to include the factory_bot_rails gem (not the factory_bot gem) in my Gemfile. I include it under the :development, :test group.

Here’s a sample Gemfile from a project with only the default gems plus a few that I added for testing.

Remember that after you add a gem to your Gemfile you’ll need to run bundle install in order to actually install the gem.

source 'https://rubygems.org'
git_source(:github) { |repo| "https://github.com/#{repo}.git" }

ruby '2.7.0'

# Bundle edge Rails instead: gem 'rails', github: 'rails/rails'
gem 'rails', '~> 6.0.2', '>= 6.0.2.2'
# Use postgresql as the database for Active Record
gem 'pg', '>= 0.18', '< 2.0'
# Use Puma as the app server
gem 'puma', '~> 4.1'
# Use SCSS for stylesheets
gem 'sass-rails', '>= 6'
# Transpile app-like JavaScript. Read more: https://github.com/rails/webpacker
gem 'webpacker', '~> 4.0'
# Turbolinks makes navigating your web application faster. Read more: https://github.com/turbolinks/turbolinks
gem 'turbolinks', '~> 5'
# Build JSON APIs with ease. Read more: https://github.com/rails/jbuilder

gem 'devise'

# Reduces boot times through caching; required in config/boot.rb
gem 'bootsnap', '>= 1.4.2', require: false

group :development, :test do
  gem 'pry'
  gem 'rspec-rails'
  gem 'capybara'
  gem 'webdrivers'
  gem 'factory_bot_rails'
end

group :development do
  # Access an interactive console on exception pages or by calling 'console' anywhere 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 background. Read more: https://github.com/rails/spring
  gem 'spring'
  gem 'spring-watcher-listen', '~> 2.0.0'
end

# Windows does not include zoneinfo files, so bundle the tzinfo-data gem
gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby]

Set up one or more factory definitions

Factory definitions are kind of the “templates” that are used for generating new objects.

For example, I have a user object that needs an email and a password, then I would create a factory definition saying “hey, make me a user with an email and password”. The actual code might look like this:

FactoryBot.define do
  factory :user do
    email { 'test@example.com' }
    password { 'password1' }
  end
end

Factory Bot is smart enough to know that when I say factory :user do, I’m talking about an Active Record class called User.

There’s a problem with this way of defining my User factory though. If I have a unique constraint on the users.email column in the database (for example), then I won’t ever be able to generate more than one User object. The first user’s email address will be test@example.com (no problem so far) but then when I go to create a second user, its email address will also be test@example.com, and if I have a unique constraint on users.email, the creation of this second record will not be allowed.

We need a way of making it so the factories’ values can be unique. One way, which I’ve done before, is to append a random number to the end of the email address, e.g. "test#{SecureRandom.hex}@example.com". There’s a different way to do it, though, that I find nicer. That way is to use another gem called Faker.

Install Faker

Just like I showed with factory_bot_rails above, the Faker gem can be added by putting it into the :development, :test group of the Gemfile.

Then we can change our User factory definition as follows.

FactoryBot.define do
  factory :user do
    email { Faker::Internet.email }
    password { Faker::Internet.password }
  end
end

This will give us random values like eldora@jones.net and lazaromertz@ko.name.

Add the Factory Bot syntax methods to my rails_helper.rb file

The syntax for actually using a Factory Bot factory in a test is as follows:

FactoryBot.create(:user)

There’s nothing wrong with this, but I find that these FactoryBot are so numerous in my test files that their presence feels a little noisy.

There’s a way to make it so that instead we can just write this:

create(:user)

The way to do that is to add a bit of code to spec/rails_helper.rb.

RSpec.configure do |config|
  config.include FactoryBot::Syntax::Methods
end

(You don’t actually add the RSpec.configure do |config| to the spec/rails_helper.rb file. It’s already there. I’m just including it here to show that that’s the block inside of which the config.include FactoryBot::Syntax::Methods line goes.)

What to do next

If you’re curious how to put Factory Bot together with the other testing tools to write some complete Rails tests, I might suggest my Rails testing “hello world” tutorial using RSpec and Capybara.

The difference between system specs and feature specs

If you’re like me, you might have found the difference between RSpec’s “feature specs” and “system specs” to be a little too nuanced to be easily understandable. Here’s my explanation of the difference between the two.

Two levels of Rails tests

For some background, I want to talk about the main two types of Rails tests that I use, and where feature specs, the predecessor to system specs, come intro the picture.

I think of Rails tests as existing on two basic levels.

High-level, course-grained

One level of tests is at a high level. These tests simulate a user visiting pages, filling in forms, clicking links and buttons, etc. Different people use different terms for these tests including “integration test”, “end-to-end test” and “acceptance test”.

Because these types of tests are often expensive to write and run, they tend not to cover every nook and cranny of the application’s behavior.

In RSpec terminology, these types of tests have historically been called feature specs.

Low-level, fine-grained

The other level of tests is at a lower level. There are all kinds of tests you could possibly write with Rails/RSpec (model specs, request specs, view specs, helper specs) but I tend to skip most of those in most scenarios and only write model specs.

From feature spec to system spec

The backstory

When Rails 5.1 came out in April 2017, one of the features it introduced was system tests. Here’s why this was done.

By default Rails ships with Minitest. The inclusion of Minitest provides a way to write model tests and certain other kinds of tests, but it historically hasn’t provided a way to write full-blown end-to-end without doing some extra work yourself, like bringing Capybara and Database Cleaner into the picture. This is the rationale for Rails 5.1’s addition of system tests, in my understanding. (To be clear, we’re still talking about system tests, not system specs.)

System specs wrap system tests

According to the RSpec docs, “System specs are RSpec’s wrapper around Rails’ own system tests.” This means that it’s no longer required to explicitly include the Capybara gem, and because system tests are already run inside a transaction, you don’t need Database Cleaner.

Summary

System specs are a wrapper around Rails’ system tests. The benefits of using system specs instead of feature specs is that you don’t have to explicitly include the Capybara gem, nor do you have to use Database Cleaner.

How to restart Sidekiq automatically for each deployment

This post will cover why it’s necessary to restart Sidekiq for each deployment as well as how to achieve it.

Why it’s necessary to restart Sidekiq for each deployment

Example: database column name change

Let’s say you have a model in your Rails application called Customer. The corresponding customers table has a column that someone has called fname.

Because you’re not a fan of making your code harder to work with through overabbreviation, you decide to alter the customers table and rename fname to first_name. (You also change any instances of fname to first_name in the Rails code.) You deploy this change and the database migration runs in production.

Problem: out-of-date Sidekiq code

Unfortunately, unless you restart the Sidekiq process, there will be a problem. At the time the Sidekiq process was started (and by “Sidekiq process” I of course mean the thing that runs when you run bundle exec sidekiq), the Rails code referred to a column called fname.

If you changed the column name without restarting Sidekiq, the code the Sidekiq process is running will be referring to the non-existent fname column and your application will break.

The fix is simple: restart Sidekiq each time you deploy. The way to achieve this, however, is not so simple.

How to get Sidekiq to restart

systemd and systemctl

Before we get into the specifics of how to get Sidekiq to restart, a little background is necessary.

You’ve probably run Linux commands before that look something like sudo service nginx restart. I’ve been running such commands for many years without understanding what the “service” part is all about.

The “service” part is related to a pair of Linux concepts, systemd and systemctl. In the words of the DigitalOcean article I just linked, systemd is “an init system and system manager” and systemctl is “the central management tool for controlling the init system”. To put it in something closer to layperson’s terms, systemd and systemctl are a way to manage processes in a convenient way.

Using systemd to manage Sidekiq

If we register Sidekiq as a service with systemd, we gain the ability to run commands like service sidekiq start, service sidekiq restart and service sidekiq stop.

Once we have that ability, restarting Sidekiq on each deployment is as simple as adding service sidekiq restart as a step in our deployment script.

Adding a sidekiq.service file

The first step in telling systemd about Sidekiq is to put a sidekiq.service file in a certain directory. Which directory depends on which Linux distribution you’re using. I use Ubuntu so my file goes at /lib/systemd/system/sidekiq.service.

For the contents of the file you can use this sidekiq.service file from the Sidekiq repo as a starting point.

Important modifications to make to sidekiq.service

Change WorkingDirectory from /opt/myapp/current to the directory where your app is served from.

Change User=deploy and Group=deploy to whatever user and group you use for deployment.

If you’re using a version of Sidekiq that’s earlier than 6.0.6, you’ll need to change Type=notify to Type=simple and remove the WatchdogSec=5 line.

Bringing in environment variables

If your application depends on environment variables, which it of course almost certainly does, the Sidekiq process will need to be aware of those environment variable values in order to run.

There are multiple ways to pull environment variables into the Sidekiq service. In my case I did so by creating a file called /etc/profile.d/sidekiq_env (the name and location are arbitrary) and adding a line EnvironmentFile=/etc/profile.d/sidekiq_env to my sidekiq.service. Here’s a sample sidekiq_env file so you know how it’s formatted:

RAILS_ENV=production
RAILS_FORCE_SSL=true
RAILS_SKIP_MIGRATIONS=false
# etc

Trying out your Sidekiq service

Once you’ve done all the above you can run systemctl enable sidekiq and then service sidekiq start. If you get something other than no output, something is wrong. If you get no output, the service theoretically started successfully, although you won’t know for absolute certain until you verify by running a job while tailing the logs.

You can tail the logs by running journalctl -u sidekiq -f. I verified my Sidekiq systemd setup by watching the output of those logs while invoking a Sidekiq job.

Troubleshooting

If everything is working at this point, congratulations. In the much more likely case that something is wrong, there are a couple ways you can troubleshoot.

First of all, you should know that you need to run systemctl daemon-reload after each change to sidekiq.service in order for the change to take effect.

One way is to use the output from the same journalctl -u sidekiq -f command above. Another is to run systemctl status sidekiq and see what it says.

If Sidekiq doesn’t run properly via systemd, try manually running whatever command is in ExecStart, which, if you used the default, is /usr/local/bin/bundle exec sidekiq -e production. That command should of course work, so if it doesn’t, then there’s a clue.

But it is possible for the command in ExecStart to work and for the systemd setup to still be broken. If, for example, you have your environment variables loaded properly in the shell but you don’t have environment variables loaded properly in your sidekiq.service file, the service start sidekiq command won’t work. Examine any Rails errors closely to determine whether the problem might be due to a missing environment variable value.

Bonus: restarting Sidekiq via Ansible

If you happen to use Ansible to manage your infrastructure like I do, here’s how you can add a task to your Ansible playbooks for restarting Sidekiq.

The restart task itself looks like this:

- name: Restart sidekiq
  service:
    name: sidekiq
    state: restarted
    daemon_reload: yes

This task alone wasn’t enough for me though. I wanted to add a task to copy the sidekiq.service file. I also needed to add a task to enable the Sidekiq service.

tasks:
  - name: Copy sidekiq.service
    template:
      src: sidekiq.service
      dest: /lib/systemd/system/sidekiq.service
      force: yes
      owner: root
      group: root
      mode: 0644

  - name: Enable sidekiq
    service:
      name: sidekiq
      enabled: yes

  - name: Restart sidekiq
    service:
      name: sidekiq
      state: restarted
      daemon_reload: yes

Good luck

Good luck, and please leave a comment if you have troubles or need clarification.

How to launch an EC2 instance using Ansible

What this post covers

In this post I’m going to show what could be considered a “hello world” of Ansible + AWS, using Ansible to launch an EC2 instance.

Aside from the time required to set up an AWS account and install Ansible, you should be able to get your EC2 instance running in 20 minutes or less.

Why Ansible + AWS for Rails hosting?

AWS vs. Heroku

For hosting Rails applications, the service I’ve reached for the most in the past is Heroku.

Unfortunately it’s not always possible or desirable to use Heroku. Heroku can get expensive at scale. There are also sometimes legal barriers due to e.g. HIPAA.

So, for whatever reason, AWS is sometimes a more viable option than Herokou.

The challenges with AWS + Rails

Unfortunately once you leave Heroku and enter the land of AWS, you’re largely on your own in many ways. Unlike with Heroku, there’s not one single way to do your deployment. There are basically infinite possible ways.

One way is to deploy manually, but that has all the disadvantages you can imagine a manual solution would have, such as, most obviously, a bunch of manual work to do each time you deploy.

Another option is to use Elastic Beanstalk. Elastic Beanstalk is kind of like AWS’s answer to Heroku, but it’s not nearly as nice as Heroku, and customizations can be a little tricky/hacky.

The advantages of using Ansible

I’ve been using Ansible for the last several months both on a commercial project and for my own personal projects.

If you’re not familiar with Ansible, here’s a description from the docs: “Ansible is an IT automation tool. It can configure systems, deploy software, and orchestrate more advanced IT tasks such as continuous deployments or zero downtime rolling updates.”

Ansible is often seen mentioned with similar tools like Puppet and Chef. I went with Ansible because among Ansible, Puppet, and Chef, Ansible had documentation that I could actually comprehend.

I’ve personally been using Ansible for two things so far: 1) provisioning EC2 instances and 2) deploying my Rails application. When I say “provisioning” I mainly mean spinning up an EC2 instance and installing all the software on it that my Rails app needs, like PostgreSQL, Bundler, Yarn, etc.

I like using Ansible because it allows me to manage my infrastructure using (at least to an extent so far) infrastructure as code. Rather than e.g. manually installing PostgreSQL and Bundler each time I provision a new EC2 instance, I write playbooks comprised of tasks (playbook and task are Ansible terms) that say things like “install Postgresql” and “install the Bundler gem”. This makes the cognitive burden of maintenance way lower and it also makes my infrastructure setup less of a black box.

Instructions for provisioning an EC2 instance

Before you start you’ll need to have Ansible installed and of course have an AWS account available for use.

For this exercise we’re going to create two files. The first will be an Ansible playbook in a file called launch.yml which can be placed anywhere on your filesystem.

The launch playbook

Copy and paste from the content below into a file called launch.yml placed, again, wherever you want.

Make careful note of the region entry. I use us-west-2, so if you use that region also, you’ll need to make sure to look for your EC2 instance in that region and not in another one.

Also make note of the image entry. I believe EC2 images can vary from region to region, so make sure that the image ID you use does in fact exist in the region you use.

Lastly, replace my_ssh_key_name with the full path to whatever SSH key you normally use to SSH into your EC2 instances, for example, ~/.ssh/aws-key.

---
- hosts: localhost
  gather_facts: false
  vars_files:
    - vars.yml

  tasks:
    - name: Provision instance
      ec2:
        aws_access_key: "{{ aws_access_key }}"
        aws_secret_key: "{{ aws_secret_key }}"
        key_name: my_ssh_key_name
        instance_type: t2.micro
        image: ami-0d1cd67c26f5fca19
        wait: yes
        count: 1
        region: us-west-2

The vars file

Rather than hard-coding the entries for aws_access_key and aws_secret_key, which would of course be a bad idea if we were to commit our playbook to version control, we can have a separate file where we keep secret values. This separate file can either be added to a .gitignore or managed with something called Ansible Vault (which is outside the scope of this post).

Create a file called vars.yml in the same directory where you put launch.yml. This file will only need the two lines below. You’ll of course need to replace my placeholder values with your real AWS access key and secret key.

---
aws_access_key: XXXXXXXXXXXXXXXX
aws_secret_key: XXXXXXXXXXXXXXXX

The launch command

With our playbook and vars file in place, we can now run the command to execute the playbook:

$ ansible-playbook -v launch.yml

You’ll probably see a couple warnings including No config file found; using defaults and [WARNING]: No inventory was parsed, only implicit localhost is available. These are normal and can be ignored. In many use cases for Ansible, we’re running our playbooks against remote server instances, but in this case we don’t even have any server instances yet, so we’re just running our playbook right on localhost. For whatever reason Ansible feels the need to warn us about this.

Verification

If you now open up your AWS console and go to EC2, you should now be able to see a fresh new EC2 instance.

To me this sure beats manually clicking through the EC2 instance launch wizard.

Good luck, and if you have any troubles, please let me know in the comments.

How to do multi-step forms in Rails

Two kinds of multi-step forms

The creation of multi-step forms is a relatively common challenge faced in Rails programming (and probably in web development in general of course). Unlike regular CRUD interfaces, there’s not a prescribed “Rails Way” to do multi-step forms. This, plus the fact that multi-step forms are often inherently complicated, can make them a challenge.

Following is an explanation of I do multi-step forms. First, it’s helpful to understand that there are two types of them. There’s an easy kind and a hard kind.

After I explain what the easy kind and the hard kind of multi-step forms are, I’ll show an illustration of the hard kind.

The easy kind

The easy kind of multi-step form is when each step involves just one Active Record model.

In this scenario you can have a one-to-one mapping between a controller and a step in the form.

For example, let’s say that you have a form with two steps. In the first step you collect information about a user’s vehicle. The corresponding model to this first step is Vehicle. In the second step you collect information about the user’s home. The corresponding model for the second step is Home.

Your multi-step form code could involve two controllers, one called Intake::VehiclesController and the other called Intake::HomesController. (I’m making up the Instake namespace prefix because maybe we’ll decide to call this multi-step form the “intake” form.)

To be clear, the Intake::VehiclesController would exist in addition to the main scaffold-generated VehiclesController, not instead of it. Same of course with the Intake::HomesController. The reason for this is that it’s entirely likely that the “intake” controllers would have different behavior from the main controllers for those resources. For example, Intake::VehiclesController would probably only have the new and create actions, and none of the other ones like delete. Intake::VehiclesController#create action would also probably have a redirect_to that sends the user to the second step, Intake::HomesController#new, once the vehicle information is successfully collected.

To summarize the solution to the easy type of multi step form: For each step of your form, create a controller that corresponds to the Active Record model that’s associated with that step.

Again, this solution only works if your form steps and your Active Record models have a one-to-one relationship. If that’s not the case, you’re probably not dealing with the easy type of multi-step form. You’re probably dealing with the hard kind.

The hard kind

The more difficult kind of multi-step form is when there’s not a tidy one-to-one mapping between models and steps.

Let’s say, for example, that the multi-step form needs to collect user profile/account information. The first step collects first name and last name. The second step collects email and password. All four of these attributes (first name, last name, email and password) exist on the same model, User. How do we validate the first name and last name attributes in the first step when the User model wants to validate a whole User object at a time?

The answer is that we create two new concepts/objects called, perhaps, Intake::UserProfile and Intake::UserAccount. The Intake::UserProfile object knows how to validate first_name and last_name. The Intake::UserAccount knows how to validate email and password. Only after each form step is validated do we attempt to save a User record to the database.

If you found the last paragraph difficult to follow, it’s probably because I was describing a scenario that isn’t very common in Rails applications. I’m talking about creating models that don’t inherit from ActiveRecord::Base but rather that mix in ActiveModel::Model in order to gain some Active-Record-like capabilities.

All this is easier to illustrate with an example than to describe with words, so let’s get into the details of how this might be accomplished.

The tutorial

Overview

This tutorial will illustrate a multi-step form where the first step collects “user profile” information (first name and last name) and the second step collects “user account” information (email and password).

Although I’m aware of the Wicked gem, my approach doesn’t use any external libraries. I think gems lend themselves well to certain types of problems/tasks, like tasks where a uniform solution works pretty well for everyone. In my experience multi-step forms are different enough from case to case that a gem to “take out the repetitive work” doesn’t really make sense because most of the time-consuming work is unique to that app, and the rest of the work is easily handled by the benefits that Rails itself already provides.

Here’s the code for my multi-step form, starting with the user profile model.

The user profile model

What will ultimately be created in the database as a result of the user completing the multi-step form is a single User record.

For each of the two forms (again, user profile and user account) we want to be able to validate the form but we don’t necessarily want to persist the data yet. We only want to persist the data after the successful completion of the last step.

One way to achieve this is to create forms that don’t connect to an ActiveRecord::Base object, but instead connect to an ActiveModel::Model object.

If you mix in ActiveModel::Model into a plain old Ruby object, you gain the ability to plug that object into a Rails form just as you would with a regular ActiveRecord object. You also gain the ability to do validations just as you would with a regular ActiveRecord object.

Below I’ve created a class that will be used in the user profile step. I’ve called it UserProfile and put it under an arbitrarily-named Intake namespace.

module Intake
  class UserProfile
    include ActiveModel::Model
    attr_accessor :first_name, :last_name
    validates :first_name, presence: true
    validates :last_name, presence: true
  end
end

The user profile controller

This model will connect to a controller I’m calling UserProfilesController. Similar to how I put the UserProfile model class inside a namespace, I’ve put my controller class inside a namespace just so my root-level namespace doesn’t get cluttered up with the complex details of my multi-step form.

I’ve annotated the controller code with comments to explain what’s happening.

module Intake
  class UserProfilesController < ApplicationController
    def new
      # An instance of UserProfile is created just the
      # same as you would for any Active Record object.
      @user_profile = UserProfile.new
    end

    def create
      # Again, an instance of UserProfile is created
      # just the same as you would for any Active
      # Record object.
      @user_profile = UserProfile.new(user_profile_params)

      # The valid? method is also called just the same
      # as for any Active Record object.
      if @user_profile.valid?

        # Instead of persisting the values to the
        # database, we're temporarily storing the
        # values in the session.
        session[:user_profile] = {
          'first_name' => @user_profile.first_name,
          'last_name' => @user_profile.last_name
        }

        redirect_to new_intake_user_account_path
      else
        render :new
      end
    end

    private

    # The strong params work exactly as they would
    # for an Active Record object.
    def user_profile_params
      params.require(:intake_user_profile).permit(
        :first_name,
        :last_name
      )
    end
  end
end

The user profile view

Similar to how the user profile controller is very nearly the same as a “regular” controller even though no Active Record class or underlying database table is involved, the user profile form markup is indistinguishable from the code that would be used for an Active-Record-backed class.

<h2>User Profile Information</h2>

<%= form_with(model: @user_profile, url: local: true) do |f| %>
  <% if @user_profile.errors.any? %>
    <div id="error_explanation">
      <b><%= pluralize(@user_profile.errors.count, "error") %> prohibited this user profile from being saved:</b>

      <ul>
        <% @user_profile.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="form-group">
    <%= f.label :first_name %>
    <%= f.text_field :first_name, class: 'form-control' %>
  </div>

  <div class="form-group">
    <%= f.label :last_name %>
    <%= f.text_field :last_name, class: 'form-control' %>
  </div>

  <%= f.submit 'Next Step', class: 'btn btn-primary' %>
<% end %>

The user account model

The user account model follows the exact same principles as the user profile model. Instead of inheriting from ActiveRecord::Base, it mixes in ActiveModel::Model.

module Intake
  class UserAccount
    include ActiveModel::Model
    attr_accessor :email, :password
    validates :email, presence: true
    validates :password, presence: true
  end
end

The user account controller

The user account controller differs slightly from the user profile controller because the user account controller step is the last step of the multi-step form process. I’ve added annotations to this controller’s code to explain the differences.

module Intake
  class UserAccountsController < ApplicationController
    def new
      @user_account = UserAccount.new
    end

    def create
      @user_account = UserAccount.new(user_account_params)

      if @user_account.valid?

        # The values from the previous form step need to be
        # retrieved from the session store.
        full_params = user_account_params.merge(
          first_name: session['user_profile']['first_name'],
          last_name: session['user_profile']['last_name']
        )

        # Here we finally carry out the ultimate objective:
        # creating a User record in the database.
        User.create!(full_params)

        # Upon successful completion of the form we need to
        # clean up the session.
        session.delete('user_profile')

        redirect_to users_path
      else
        render :new
      end
    end

    private

    def user_account_params
      params.require(:intake_user_account).permit(
        :email,
        :password
      )
    end
  end
end

The user account view

Like the user profile view, the user account view is indistinguishable from a regular Active Record view.

<h2>User Account Information</h2>

<%= form_with(model: @user_account, local: true) do |f| %>
  <% if @user_account.errors.any? %>
    <div id="error_explanation">
      <b><%= pluralize(@user_account.errors.count, "error") %> prohibited this user account from being saved:</b>

      <ul>
        <% @user_account.errors.full_messages.each do |message| %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="form-group">
    <%= f.label :email %>
    <%= f.email_field :email, class: 'form-control' %>
  </div>

  <div class="form-group">
    <%= f.label :password %>
    <%= f.password_field :password, class: 'form-control' %>
  </div>

  <%= f.submit 'Save and Finish', class: 'btn btn-primary' %>
<% end %>

Routing

Lastly, we need to tie everything together with some routing directives.

Rails.application.routes.draw do
  namespace :intake do
    resources :user_profiles, only: %i[new create]
    resources :user_accounts, only: %i[new create]
  end
end

Takeaways

There are two types of multi-step forms. The easy kind is where each step corresponds to a single Active Record model. In those cases you can use a dedicated controller per step and redirect among the steps. The harder kind is where there’s not a one-to-one mapping. In those cases you can mix in ActiveModel::Model to lend Active-Record-like behaviors to plain old Ruby objects.

In addition to the bare functionality I described above, you can imagine multi-step forms involving more sophisticated requirements, like the ability to go back to previous steps, for example. I didn’t want to clutter my tutorial with those details but I think those behaviors would be manageable enough to add on top of the underlying overall approach I describe.

As a last side note, you don’t have to use session storage to save the data from each form step until the final aggregation at the end. You could store that data any way you want, including using a full-blown Active Record class for each step. There are pros and cons to the various possible ways of doing it. One thing I like about this approach is that I don’t have any “residual” data lying around at the end that I have to clean up. I can imagine other scenarios where other approaches would be more appropriate though, e.g. multi-step forms that the user would want to be able to save and finishing filling out at a later time.

The most important thing in my mind is to keep the multi-step form code easily understandable and to make its code sufficiently modularized (e.g. using namespaces) that it doesn’t clutter up the other concerns in the application.

When used intelligently, Rails concerns are great

Is a knife a good thing?

There seem to be two main camps regarding Rails concerns. One camp says concerns are good. The other camp says concerns are bad.

I don’t buy this dichotomy. To state what ought to be obvious, not everything is either good or bad.

I’ll make a small digression to illustrate my point, using the example of a knife.

You could use a knife to stab me in the leg, or you could use a knife to whittle me a magnificent wooden sculpture.

Or, if you weren’t too familiar with knives, you could try to use a knife instead of a spoon in a misguided attempt to eat a bowl of soup.

You get the idea.

I want to discuss where concerns might be a good idea and where they might not be. In order to do this it might be helpful to start by articulating which types of problems people tend to use concerns to try to solve.

The problems that people try to solve with concerns

There are two problems I’m aware of that people try to use concerns to solve.

  1. Models that are too big to be easily understood
  2. Duplication across models

Both these problems could be solved in a number of ways, including by using concerns.

(Another way could be to decompose the model object into a number of smaller plain old Ruby objects. Yet more options include service objects or Interactors. All these approaches have pros and cons and could be used appropriately or inappropriately.)

The objections some programmers have to concerns

Here are some objections to concerns that I’ve read. Some I agree with. Some I don’t.

Concerns are inheritance, and composition is better than inheritance

I totally agree with this one. An often-repeated piece of advice in OOP is “prefer composition over inheritance”. I think it’s good advice.

Having said that, the advice is not “don’t ever use inheritance!” There are a lot of cases where inheritance is more appropriate than composition, even if inheritance isn’t the first thing I reach for. In the same way, the argument that “concerns are inheritance and composition is better than inheritance” does not translate to “concerns are always bad” any more than “prefer composition over inheritance” means “inheritance is always bad”.

Concerns don’t necessarily remove dependencies, they just spread them across multiple files

I agree with this point as well. It’s totally possible to slice a model up into several concerns and end up with the result that the “after” code is actually harder to understand than the “before” code.

My response to this point would be: If you’re going to use concerns, don’t use them like that!

Concerns create circular dependencies

As far as I can reason, this is true. A well-defined inheritance relationship doesn’t create a circular dependency because the parent class doesn’t know anything about the child class. Similarly, in a well-defined composition relationship, the “finer-grained” class doesn’t know about the “coarser-grained” one.

Concerns often don’t work this way. Concerns often refer to the particulars of the models that use them.

But I would say: Why, exactly, is this sort of circular dependency bad? And if I choose not to use a concern in a particular case because I want to avoid a circular dependency, what exactly my alternative? The drawbacks of whatever I use instead of a concern aren’t automatically guaranteed not to be worse than the drawbacks of using a concern.

When code is spread across multiple files, it can be unclear where methods are defined

This argument has some merit, although a very small amount. This argument could be made equally for not just concerns but for inheritance as well. So it’s really not an argument against concerns specifically, it’s an argument against concerns and inheritance equally, or perhaps an argument against spreading code across multiple files in general.

But in any case, the problem can be easily overcome using search.

What I do when I encounter fat or duplicative models

When I encounter a fat or duplicative model, concerns are pretty much never the first solution that enters my mind.

My first instinct, again, is to consider decomposing my model into some number of plain old Ruby objects. I think sometimes Rails developers forget that plain old OOP, using plain old objects, can get you a very long way.

I often find that a large class will have one or more “hidden abstractions” inside it. There’s some sub-concept hiding in the class that doesn’t yet have a name or a cohesive bundle of code. It’s there, it’s just hiding.

In those cases I try to come up with a name for the hidden abstraction. Then I create a class named after that abstraction and move the appropriate code into my new class.

I don’t always find a hidden abstraction though. Sometimes some of the bloat is just some extra flab that neither fits neatly into the model it’s in nor makes sense as a standalone concept. This is when I (sometimes) think of using a concern.

When I use concerns

In DHH’s post about concerns he says “Concerns are also a helpful way of extracting a slice of model that doesn’t seem part of its essence” (emphasis mine). I think that’s a great way to put it.

Good use cases for concerns

To me, the perfect use case for a concern is when I have, say, 10 methods on a model that have a high level of cohesion with one another and then 2 methods that relate to each other but don’t seem to be part of the model’s essence.

If those 2 odd-man-out methods could make a new object of their own, great. I’ll do that. If not, I might consider moving them to a concern.

And if multiple models require highly symmetrical behavior (e.g. some certain file attachment behavior), I’d usually rather factor that symmetrical behavior into a concern than to an inheritance tree. And usually a concern in these cases fits much more tidily over the models than trying to create a composition relationship out of it.

Lastly, I don’t want to give the impression that I use concerns all over the place. In general when I’m programming, concerns are among the last things I reach for, not the first.

My advice concerning concerns

I think concerns are fine and, in certain scenarios, great. That of course doesn’t mean I think concerns are the best solution to every problem.

When faced with any programming problem, take stock of the tools in your toolbox and use what seems appropriate. Object composition can be good. Inheritance can be good. Concerns can be good too.

Understanding Rails secrets/credentials

What this feature is for

The credentials feature is a way of storing secrets that you don’t want to keep in plaintext, like AWS credentials for example. (In fact, the one and only thing I keep in my main Rails project’s credentials are my Active Storage AWS credentials.)

Why the credentials feature is difficult to learn about

I personally have found Rails credentials really hard to understand. I think there are three reasons why this is.

  1. The official Rails docs about the credentials feature are a little terse and not easily discoverable.
  2. The feature changed somewhat drastically from Rails 4 to Rails 5, even changing names from “secrets” to “credentials”.
  3. I only need to use the feature once in a while, when I’m deploying an application for the first time, meaning there’s lots of time in between to forget everything I knew.

How to work with credentials without frustration

There are perhaps five important things to understand about credentials:

  • Where they’re stored
  • How the master key works
  • How editing credentials work
  • What the deal is with secrets.yml
  • The steps you need to take to set up credentials on a fresh production machine
  • Credentials are stored in config/credentials.yml.enc

    At the risk of stating the obvious, your secrets are stored in config/credentials.yml.enc. That file is encrypted.

    There of course needs to be a way to securely decrypt this encrypted file. That’s done using something called a master key. The master key is just a hash string that gets stored in one of two places: a file called config/master.key (which should NOT be committed to version control) or a RAILS_MASTER_KEY environment variable.

    The development master key and production master key are the same by default

    A key thing to understand, which I found counterintuitive, is that unless you go out of your way to configure it differently, the master key you use in production should be the same as the master key you use in development. If your development master key is stored in config/master.key, create an identical config/master.key on production, containing the same exact key. If your development master key is stored in the RAILS_MASTER_KEY environment variable, set the production RAILS_MASTER_KEY to the exact same value.

    I found this counterintuitive because usually I try to make all my passwords, etc. different for each environment I have. I thought I would need to create a different master key for my production environment. No, I need to not create a different master key.

    (After I originally posted this article, it was pointed out to me that it is possible to configure different keys per environment. According to this article, keys can be configured specific to e.g. production by running rails credentials:edit --environment production.)

    The credentials.yml.enc file is edited in a special way

    Since it’s encrypted, the config/credentials.yml.enc file can’t be edited directly. It can only be edited using the rails credentials:edit command.

    What often throws me for a loop is that a prerequisite to using rails credentials:edit is having the EDITOR environment variable set, which on a fresh production machine I usually don’t. I’m a Vim guy, so I run export EDITOR=vim and then I’m good to go. Then I can run rails credentials:edit and the command will open the credential file, decrypted, in Vim.

    secrets.yml is obsolete

    If you find something online that refers to secrets.yml, you’re looking at an old post. Before Rails 5.2, there was a secrets.yml and secrets.yml.enc instead of the new credentials-related files. Don’t make the mistake of conflating Rails secrets with Rails credentials (like I did several times before learning better!).

    The steps for setting up credentials in production

    1. Take the same master key you’re using in development and put it either in config/master.key or the RAILS_MASTER_KEY environment variable.
    2. Set the EDITOR environment variable to your favorite terminal-based editor.
    3. Run rails credentials:edit to verify that your master key is working properly.

    Helpful links

    I hope my credentials guide is the new best guide on the internet but I’ll link to the sources that helped me put this together.

    Best of luck with your credential management endeavors.

What kinds of Rails tests I write and what kinds I don’t

The challenge of deciding what kinds of tests to write

There are a lot of different kinds of tests a developer could possibly write. In RSpec there are model specs, feature specs, view specs, helper specs, routing specs, controller specs, and request specs. Do you need to write all these different types of tests? If not, which ones should you skip?

The two types of tests I write

In my Rails apps I usually only write two kinds of tests. To use framework-agnostic language, I write model tests and acceptance tests. To use RSpec-specific language, I write model specs and feature specs. Let’s talk about each of these two in a little more detail.

Model tests are relatively isolated. A model test will test the behavior of a particular Ruby class in my Rails application, e.g. the Customer model. Model tests are what give me confidence that my features are working at a fine grain. (I often use the terms “class” and “model” somewhat interchangeably since a Rails model takes the form of a Ruby class.) For example, if I have a method that returns the grand total of the payments a certain customer has made, I’ll write a model test for that method.

What about acceptance tests? Whereas model tests are relatively isolated, acceptance tests are the opposite; they’re relatively integrated. An acceptance test exercises the whole stack, browser and all. Acceptance tests give me confidence that everything works together. For example, if I have CRUD functionality for a Customer resource, I’ll write acceptance tests for creating a customer, updating a customer, and deleting a customer.

A note on terminology

As of the time of this writing there not complete consensus in the testing world on testing terminology (and I don’t expect this to change anytime soon). For example, some developers might consider the terms end-to-end-test, integration test, and acceptance test to refer to basically the same thing, whereas other developers might consider these three terms to refer to three completely distinct types of tests.

What does this mean for you in terms of your testing journey? It means that if you, for example, see a reference to end-to-end tests that doesn’t square with your understanding of the term end-to-end tests, that doesn’t necessarily mean that your understanding is mistaken, it just means that not everyone in the testing world talks about the same concepts using the same exact terms. I think understanding this communication challenge is helpful since otherwise one might get distracted by all the apparent discrepancies.

Testing terminology + RSpec

How do model tests and acceptance tests map to RSpec? Model tests—or model “specs” in RSpec terminology—are the tests that live in the spec/models directory. Nothing but bare RSpec is needed in order to write model specs. What the outside world calls acceptance tests (or again, integration tests or end-to-end tests) are known in RSpec as feature specs. Since feature specs exercise features via the browser, the Capybara library (a library that allows us to manipulate the browser using Ruby) is necessary.

Why, by the way, is an RSpec test called a “spec” instead of a “test”? The idea is that each RSpec file is an “executable specification”. Each test case is actually called an example in RSpec terminology, an example of how that feature is supposed to behave. A set of examples makes up a spec. Personally, I’m willing to play along with this terminology to some extent, but I frankly find it kind of annoying. You’ll find that most of the time I say “test” instead of “spec” or “example”.

The types of tests I don’t write

I typically don’t write view specs, routing specs, or request specs/controller specs. Let’s discuss each of these.

Request specs/controller specs

First, a bit of terminology: request specs and controller specs are two very slightly different things, although for our purposes we can treat the two terms as meaning the same thing. A close enough approximation to the truth is that “controller spec” is the old name and “request spec” is the new name.

I usually don’t write controller/request specs (although I do sometimes). The reason is that I try to have as little code in my controllers as possible. For the most part, my controllers don’t do anything, they just call model objects that do things. Any code that might have been covered by a controller/request spec, I prefer to cover via a feature spec.

View specs, routing specs

I don’t write view specs and I don’t believe I’ve ever encountered anyone who does. The reason is that I’ve never been able to conceive of how a view spec could have value that a feature spec doesn’t already provide.

I also don’t write routing specs, and for the same reason. I find them to be redundant to feature specs.

Conclusion

If you’re just getting started with Rails/RSpec testing and you’re wondering which types of tests to focus on, I would recommend focusing on model specs and feature specs.