Introduction

This article kicks off a series of articles that will show you how to build an RESTful API from scratch using Rails 5. This series will focus on taking an iterative (agile) approach to development. We will also follow basic TDD (test driven development) principles, writing tests before writing any code. We will show you how to build the API, version it, and later show you how to add both non-breaking changes (that don't require a new version) as well as breaking changes. Finally, we will show you how to create an API client and cover advanced topics such signing your API requests.

One more thing, this series is completely free for all of our users (though pro users still get early access), so enjoy!

This article will go over an overview of the API and cover the Rails Application Setup. Let's get started!

API Overview

The API we will be building is a CRM tool. For those of you that don't know what a CRM is, CRM means Customer Relationship Management. CRM tools provide the ability to track and store relevant information about a customer. Our tool will track the user's contact information, incoming/outgoing communications, and more.

Before we can start building our API, we need to know the business requirements. I have listed these below.

Multi-Tenant Aware
Multiple independent clients can store data and interact with this API. A client may have one or more users that can each add their own data and read/write to their client's stored data.
General Information About the Customer
We wish to track a customer's first name, last name, current address, current phone number, and current email address. We should have a way to store what type of address, phone number, and email address it is. For instance, home address, work email, work phone number, etc.
Historical Information
Our API should store previous addresses, phone numbers, and email addresses.
Audit Log
Our API should store historical changes for audit purposes.
Communications
Our API should provide a mechanism to take notes on communications with the customer.
Administrative API
The API should have a way to add and remove clients, their users, and all of their data. There should be a mechanism to either soft delete or hard delete the client, so that clients can be restored prior to being hard deleted.

Based on the above requirements, we can now do some architectural planning ahead of time that will help us design and develop our API. Thinking about architecture ahead of time can help you find a good place to start development. It can also save you trouble down the road. First, let's figure out what models we will need based on the requirements above.

  • Client - Possible database fields: name. Looking at the features above, we know that we need to be multi-tenant aware. The client in this case represents the 'tenant'. We add a name to the clients table to give each client a name.
  • ApiKey - Possible database fields: client_id, name, and key. API keys represent the users of our system. The client_id stores the client's id. In order to fulfill the Administrative API portion of the requirements, we will make our client_id nullable. When the client_id is set to null, it is assumed that the API key belongs to an admin. We will discuss possible ramifications of this in a future article, but for now, this is the simplest way to implement this requirement. The name column allows us to name each API key and the key column stores the key itself.
  • Customer - Possible database fields: client_id, first_name and last_name. The Customer model stores information about the customer. By looking at the requirements above, we know that a customer has multiple addresses, phone numbers, and email addresses. This rules out storing them in the customers table. However, we can still store the name of the customer in the first_name and last_name fields.
  • Address - Possible database fields: customer_id , address1, address2, city, state, postal_code, country, address_type, primary. Looking at the first two features, we know that we will need to track multiple addresses for a customer. The Address model gives us a way to manage that. We have all the standard address fields that are commonly used. In addition, we need a way to track the address type. For now, we will make this a free form text field called address_type to keep things simple, but we may iterate on this in the future. Finally, we have a boolean field called primary that allows us to track the user's primary address.
  • Phone - Possible database fields: customer_id, number, phone_type, primary. The Phone model allows us to manage phone numbers. Here, we store store the phone number itself in the number field. We have a phone_type field for specifying the phone type (home, work, mobile, etc.). Finally, we also have a primary field here for marking the number as the user's primary contact number.
  • Email - Possible database fields: customer_id, address, email_type, primary. The Email model allows us to manage the customer's email addresses. We will store the email address in the address field, the type of email address in the email_type field, and whether the email is the user's primary contact address or not in the primary field.
  • Communication Possibly database fields: communication_type, notes. The Communication model allows us to store notes regarding our communications with the customer. The communication_type field can be something like inbound call, outbound call, received email, etc. The notes field stores the notes that were taken as a result of this communication.

What about the audit log? We can use a library for that called PaperTrail. The paper_trail gem provides an easy way to track and store changes that take place. We will get more into PaperTrail later on.

Now that we've covered all of the possible models, let's think about the controllers and actions that our project will need. Because we are building a simple, straightforward API, this is actually pretty easy.

  • Clients - used to manipulate clients. Only accessible via administrators.
    • index - example: GET /clients - Returns a list of the clients.
    • create - example: POST /clients - Creates a new client.
    • update - example: PATCH /clients/1 - Updates an existing client.
    • destroy - example: DELETE /clients/1 - Deletes an existing client.
  • ApiKeys - The API Keys controller is used to manipulate API keys. Clients can manipulate API keys for themselves, administrators can manipulate API keys for anyone.
    • index - example: GET /api_keys - Returns a list of the clients.
    • create - example: POST /api_keys - Creates a new API key.
    • update - example: PATCH /api_keys/1 - Updates an existing API key.
    • destroy - example: DELETE /api_keys/1 - Deletes an existing API key.
  • Customers - The customers controller is used to manipulate customer data. Clients can only manipulate their own data. Admins have no access.
    • index - example: GET /customers - Returns a list of the customers for the client.
    • create - example: POST /customers - Creates a new customer for the client.
    • update - example: PATCH /customers/1 - Updates an existing customer.
    • destroy - example: DELETE /customers/1 - Deletes an existing customer.
  • Addresses - The addresses controller is used to manipulate addresses for a customer. Address requests are nested under the customers controller.
    • index - example: GET /customers/1/addresses - Returns the addresses for the specified customer.
    • create - example: POST /customers/1/addresses - Creates a new address for the customer.
    • update - example: PATCH /customers/1/addresses/1 - Updates an existing address for the customer.
    • destroy - example: DELETE /customers/1/addresses/1 - Deletes the specified addresss from the customer.
  • Phones - The phones controller is used to manipulate phones for a customer. Phone requests are nested under the customers controller.
    • index - example: GET /customers/1/phones - Returns the phone numbers for the specified customer.
    • create - example: POST /customers/1/phones - Creates a new phone numbers for the customer.
    • update - example: PATCH /customers/1/phones/1 - Updates an existing phone number for the customer.
    • destroy - example: DELETE /customers/1/phones/1 - Deletes the specified phone number from the customer.
  • Emails - The emails controller is used to manipulate emails for a customer. Email requests are nested under the customers controller.
    • index - example: GET /customers/1/emails - Returns the emails for the specified customer.
    • create - example: POST /customers/1/emails - Creates a new email for the customer.
    • update - example: PATCH /customers/1/emails/1 - Updates an existing email for the customer.
    • destroy - example: DELETE /customers/1/emails/1 - Deletes the specified email from the customer.
  • Communications - The communications controller is used to manipulate communications for a customer. communication requests are nested under the customers controller.
    • index - example: GET /customers/1/communications - Returns the communications for the specified customer.
    • create - example: POST /customers/1/communications - Creates a new communication for the customer.
    • update - example: PATCH /customers/1/communications/1 - Updates an existing communication for the customer.
    • destroy - example: DELETE /customers/1/communications/1 - Deletes the specified communication from the customer.

Great! Now let's set up our Rails application.

Rails Application Setup

For this example, we will create a brand new Rails application. Run the command below to create this application.

Terminal Commands
  
    rails new APIExample --api
  

Notice that we passed in a flag. The --api flag tells Rails that we are creating an API only project. API projects have fewer dependencies loaded and run a bit faster than regular Rails applications as a result.

Now let's switch to our APIExample project by using the cd command.

Terminal Commands
  cd APIExample

Now that we've created a new project, we need to add a few gems to our Gemfile. The gems we will be using in this example are listed below.

  • rspec-rails - RSpec is a popular testing framework for Ruby. The rspec-rails gem provides Rails specific functionality enhancements.
  • factory_girl_rails - The factory_girl_rails gem is a Rails wrapper for the popular FactoryGirl gem. FactoryGirl provides a way for us to create Factories that can be used to generate data for our tests as well as our development environment.
  • rails-controller-testing - The rails-controller-testing gem provides some extra methods to make writing controller tests easier.
  • database_cleaner - The database_cleaner gem keeps our database clean during test runs.

Now that we know what these gems do, let's add them to our Gemfile. Open up your Gemfile and modify it so that it looks like the code listed below.

Gemfile:
  
source 'https://rubygems.org'

git_source(:github) do |repo_name|
  repo_name = "#{repo_name}/#{repo_name}" unless repo_name.include?("/")
  "https://github.com/#{repo_name}.git"
end

gem 'rails', '~> 5.0.1'
gem 'sqlite3'
gem 'puma'

group :development, :test do
  gem 'byebug', platform: :mri
  gem 'listen'
  gem 'spring'
  gem 'spring-watcher-listen'
  gem 'rspec-rails'
  gem 'factory_girl_rails'
  gem 'rails-controller-testing'
  gem 'database_cleaner'
end

First, we removed all the commented out lines, since they aren't relevant to our project. Next, we removed references to versions from all of our gem entries. This is a matter of preference for me, I prefer to keep versions locked in Gemfile.lock whenever possible. Then we added the gems listed above for our development and test environments. Finally we removed a Windows specific gem at the bottom of the Gemfile. If you are using Windows ruby, you may wish to leave this in.

Now let's run bundle to install the gems.

Terminal Commands:
  
bundle
  

Next we need to configure RSpec. Run the command below to add configuration files for RSpec.

Terminal Commands:
  
  rails g rspec:install
  

This creates the spec folder along with a few configuration files. Now we can delete the test folder, since it is no longer used.

Terminal Commands:
  
  rm -rf test/
  

Now that we have RSpec installed, we need to configure our other gems. First, create a folder called support in the spec/ folder. The command below will do this for you.

Terminal Commands:
  
  mkdir spec/support
  

Now we need to make a two changes to our spec/support/rails_helper.rb file. The first change we need to make involves telling Ruby to load all *.rb files in the spec/support folder when the spec/support/rails_helper.rb file is loaded. This is so we can add custom files to spec/support and have them get included when RSpec runs. An example of what this code looks like is below.

  Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

The second thing we need to do is change the config.use_transactional_fixtures setting to false. This tells Rails not to use database transactions when running each of our tests. We are making this change because DatabaseCleaner will manage our database cleaning strategy, so we want Rails to stay out of the way.

Below is the final spec/rails_helper.rb file with both changes made and all of the comments removed for easier reading. Note that you don't have to remove the comments. I prefer to do so to make it easier on those reading this article.

spec/rails_helper.rb:
  
ENV['RAILS_ENV'] ||= 'test'
require File.expand_path('../../config/environment', __FILE__)
abort("The Rails environment is running in production mode!") if Rails.env.production?
require 'spec_helper'
require 'rspec/rails'

Dir[Rails.root.join('spec/support/**/*.rb')].each { |f| require f }

ActiveRecord::Migration.maintain_test_schema!

RSpec.configure do |config|
  config.fixture_path = "#{::Rails.root}/spec/fixtures"
  config.use_transactional_fixtures = false
  config.infer_spec_type_from_file_location!
  config.filter_rails_from_backtrace!
end
  

Now let's configure support for FactoryGirl. Create a new file called factory_girl.rb inside spec/support and modify it so that it looks like the code listed below.

spec/support/factory_girl.rb:
  
RSpec.configure do |config|
  config.include FactoryGirl::Syntax::Methods
end
  

The code above simply includes FactoryGirl's functionality in with RSpec. For example, this means we can run create(:client) for instance to create a new client.

Now we are ready to configure Database Cleaner. Create a new file in the spec/support folder called database_cleaner.rb and modify it so that it looks like the following code.

spec/support/database_cleaner.rb:
  
RSpec.configure do |config|
  config.before(:suite) do
    DatabaseCleaner.clean_with(:truncation)
  end

  config.before(:each) do
    DatabaseCleaner.strategy = :transaction
  end

  config.before(:each) do
    DatabaseCleaner.start
  end

  config.append_after(:each) do
    DatabaseCleaner.clean
  end
end
  

What does this code do? Lines 2-4 tells Database Cleaner to wipe the database before running the test suite. Lines 6-9 configures Database Cleaner to use a transaction for each of our tests. This means no data actually gets committed to our test database. lines 11-13 tells DatabaseCleaner to clean our database after each test. If we were building a regular Rails application, we would have additional code that tells Database cleaner to have different behavior whenever integration tests are run.

So what's next? In our next article we will start building out our API. The next article is already finished and is scheduled to release on Thursday February 16th, 2017. If you have any questions or comments, feel free to use the comment section below or shoot me a tweet @RichIsOnRails. Thanks for reading!