Adding a new client

Adding a new client

Adding a new client

We've got a rough specification for our first feature. Our health and safety company has a set of standard policy documents that they want to make available, read-only, to their clients.

What are we building?

So, using some of the core concepts we defined last time out, we should probably have something like this:

  • We have an organisation representing our health and safety company.
  • This organisation has some folders, each containing some documents.
  • When a new client organisation is created, we want to make those policy document folders available, but read-only, to the new client.
  • As we want to make the system as re-configurable as possible, instead of putting these rules into the "organisation modules", let's add in the concept of an "automation" that, once triggered by events elsewhere, performs the relevant actions.

Specification-driven development

I don't do "test-driven development", I do "specification-driven development". You could argue that it's just semantics, but words matter.

I want to specify what the system is supposed to do, write some code to make it happen and, importantly, stop writing code as soon as the system does what has been asked of it. A quick tidy-up and we're done. Plus the end result is usually less code which is simpler and easier to understand. And the system is fully documented[1]. Hopefully, we'll see why that happens as the story unfolds.

So let's write our specification.

The tools

We'll use RSpec[2] and Turnip[3] and instead of driving a user-interface by remote controlling a browser[4], we'll implement a HTTP/JSON API[5].

As background, this is a Rails app that uses Devise and Doorkeeper for handling authentication. Instead of dealing with all that stuff, I've written some little helper methods that generate an OAuth Access Token which we then pass into our API calls.

The specification

So, first we write our specification document. Ideally, we would talk this through with the customer themselves, listening to their descriptions of the process and typing it up into the slightly more formal "gherkin" syntax that Turnip uses.

Here's a start - in spec/features/adding_client_organisation.feature

Feature: Adding a client organisation

  Scenario: Creating the client organisation
    Given I have an admin account at a health and safety organisation
    And the organisation has a folder containing policy documents
    When I log in and add a client organisation
    Then I should see the new client organisation
	And I should see that the client organisation has read-only access to the policy documents folder
	When I look at the audit trail
    Then I should see a log of how access to this folder was granted

Let me stress that we wrote this document in conversation with the customer. Previously I mentioned things like workflows and permissions and automations, but this document makes no mention of those. Because the customer does not care how we're going to implement this. They only care that their client gets access to these documents and they have some visibility into what the system is doing.

Also let me stress that the customer will probably never look at this document again[6]. So why do we restrict ourselves in this way?

Because we do not want to start thinking about implementations at this stage[7]. We are developers. We think in terms of classes and functions and data-structures and algorithms. But we are still figuring out what needs to happen, why it needs to happen[8], not how it's going to happen.

Filling in the blanks

The customer knows their own business and (usually) knows what they want. But they're not software developers and we know that they will not capture everything that the specification will need.

For example, they are just going to take it for granted that none of this functionality is available if you're not logged in.

We will need to ask them what happens if someone logs in as a normal staff member rather than an admin. Maybe ordinary staff members are permitted to add organisations, maybe they're not; maybe they're permitted to read the audit trail, maybe they're not.

It's our duty, as developers, to think of these alternative scenarios, ask the right questions and ensure that the specification is complete. Again, the customer won't really care and will probably find us pedantic and annoying for asking these things[9] but it needs to be done.

In this case, we can flesh out our specification with a couple more scenarios.

  Scenario: Attempting to create a client organisation when not logged
    When I am not logged in
    Then I should not be able to add a client organisation 

  Scecnario: Attempting to create a client organisation when not an admin
    Given I have a non-admin account at a health and safety organisation
    When I log in
    Then I should not be able to add a client organisation 

The implementation

Now we've finished talking to the customer, we've got our specification written up, we can create spec/features/adding_client_organisation_steps.rb and take our first steps towards the implementation.

module AddingClientOrganisationSteps
  step "I have an admin account at a health and safety organisation" do
  end

  step "the organisation has a folder containing policy documents" do
  end

  step "I log in and add a client organisation" do
  end

  step "I should see the new client organisation" do
  end

  step "I should see that the client organisation has read-only access to the policy documents folder" do
  end

  step "I look at the audit trail" do
  end

  step "I should see a log of how access to this folder was granted" do
  end

  step "I am not logged in" do 
  end

  step "I should not be able to add a client organisation" do 
  end

  step "I log in" do 
  end
end

RSpec.configure { |c| c.include AddingClientOrganisationSteps }

This isn't a typical RSpec file. This is a Turnip "steps" file. If we want it to do anything, we need to add a loader clause to our spec/rails_helper.rb below the require "rspec/rails" line.

require "support/builders"
require "support/authorisation"

Dir.glob("spec/features/*_steps.rb") do |f|
  load f, true
end

The first two lines require my set of helper functions (one for OAuth2 and one for managing the database). The next loads our steps file, so when turnip finds our .feature file, it knows what to do. We also need to add --require turnip/rspec to our .rspec file in the project root.

Getting started

Let's start with the simplest case - trying to add a new organisation when we're not logged in.

  step "I am not logged in" do 
    # do nothing
  end

  step "I should not be able to add a client organisation" do
    post "/api/organisations", params: {name: "TinyCo"}
    expect(response).to be_unauthorized 
  end

An important point to note here; when we're writing out the RSpec part of the specification, we're putting ourselves in the shoes of the developer who is going to be using our system. When you start looking at the documentation for a new API, do you ever scratch your head and wonder why they made it work that way? Here, we have the opportunity to write out how we want it to work in an ideal world. In this case, everyone is used to POSTing to an end-point to create an item - so we should stick with those expectations.

When we run this spec, it obviously fails. So the next step is to add in a route and a controller.

# config/routes.rb
Rails.application.routes.draw do
  use_doorkeeper
  devise_for :users
  get "up", to: "rails/health#show", as: :rails_health_check

  namespace :api do
    resources :organisations, only: [:create]
  end
end

# app/controllers/api/organisations_controller.rb
class Api::OrganisationsController < ApplicationController
  before_action :doorkeeper_authorize!
  def create
    head :created
  end
end

The routes file has the standard stuff for loading doorkeeper, devise and the health-check end-point. Then we use an "API" namespace and define the route for accessing organisations. The organisations controller uses doorkeeper to verify the OAuth access token and has a single create action. And also note that we don't need to implement the create action - our single spec doesn't care what create actually does, it only cares that we are not allowed to call it[10].

The simplest thing that will work

The next spec is if we are logged in as a non-admin user. This is where things get a bit more complicated, as we've got some setup work to do. Returning to our spec file, we need to create a non-admin user record, get their OAuth access token and pass that in our POST request.

I'm not going to go through the mechanics of creating the Devise user model (or getting it to work with doorkeeper). Instead, I've got some helper methods (defined in spec/support/builders.rb and spec/support/authentication.rb) for setting up our data and getting an access token.

This results in the following in our steps file:

  step "I have a non admin account at a health and safety organisation" do
    @me = a_user status: "admin"
    @my_organisation = an_account name: "H&S Company"
  end

  step "I log in" do
    @my_access_token = an_access_token_for user: @me
    @auth_headers = auth_headers_for(@my_access_token)
  end

The a_user function is a builder - we can optionally supply a name, email address, password and status[11] for our user and it creates a valid ActiveRecord model for us.

def a_user first_name = "Alice", last_name = "Aardvark", email: nil, password: nil, status: "standard"
  password ||= "Password123!"
  email ||= "#{first_name.downcase}.#{last_name.downcase}@example.com"
  User.create!(first_name: first_name, last_name: last_name, email: email, password: password, password_confirmation: password, status: status)
end

We repeat this for an_account. We don't really care what an account looks like at this time, so just generate a model with a single name field and implement the an_account function to create a record.

The doorkeeper helpers create a user-specific access token that is attached to an application[12]. The final helper generates the "Authorization"[13] header that is passed in our HTTP requests.

def an_access_token user: nil, application: nil
  user ||= a_user
  application ||= a_doorkeeper_application
  Doorkeeper::AccessToken.create! resource_owner_id: user.id, application_id: application.id
end

def a_doorkeeper_application name: nil, redirect_uri: nil
  name ||= "Test Application"
  redirect_uri ||= "https://example.com"
  Doorkeeper::Application.create! name: name, redirect_uri: redirect_uri
end

def auth_headers_for(access_token)
  {Authorization: "Bearer " + access_token.token}
end

There's one more bit of setup to do. We have some authentication headers but our previous step that attempted to create an organisation passed no headers. So let's revisit those steps.

  step "I am not logged in" do 
    @auth_headers = {}
  end

  step "I should not be able to add a client organisation" do
    post "/api/organisations", params: {name: "TinyCo"}, headers: @auth_headers
    expect(response).to be_unauthorized 
  end

With all this setup done, let's try running our spec again. And, as expected, it fails with Failure/Error: expect(response).to be_unauthorized. This is because our controller is only checking to see if we've got an access token and no more.

But now we have an access token, we need to add in the check that we're actually an admin user. There are many ways to do this, and I'm sure we will revisit this later, but for now, let's do the simplest thing that makes the spec pass.

Returning to our organisations controller, we need to test if the current user is an admin - if they are, we return :created (a 201 status code), if they are not, we return :unauthorized (a 401 status code). The current_user method tells us who is currently logged in and is added to every controller in our application, thanks to Devise. But before we can use it, we need to link doorkeeper and devise.

So in config/initializers/doorkeeper.rb we add the following into the configuration clause:

  resource_owner_authenticator do
    current_user || warden.authenticate!(scope: :user)
  end

Then we make our organisations controller check the current user's status. I'm using a Rails enum for this (see the earlier footnote) meaning Rails gives us an admin? method. We test the current user and return the appropriate status code.

class Api::OrganisationsController < Api::BaseController
  before_action :doorkeeper_authorize!
  def create
    head(current_user.admin? ? :created : :unauthorized)
  end
end

Run the specs again and now they pass.

Once more, we've done the simplest thing, the least amount of work, to meet the specification.

But we've still got the biggest specification, that needs folders being set up, that creates our organisation and triggers the automations, to come. The next stage is going to be much more in-depth as we need to define a whole load of models, as well as putting together a framework for our automations and their triggers.


  1. Writing documentation is a pain. With this process, the documentation is intrinsic to how we work and it's always up to date. ↩︎

  2. The clue is in the name ↩︎

  3. Turnip allows us to write RSpec style specifications that are driven by "Gherkin", which you may recognise from the "Cucumber" Behaviour Driven Development framework. However, I really dislike Cucumber's ruby implementation, which spreads steps and instance variables all over the place. Whereas Turnip lets me write my specifications in formal english and implement them as a single RSpec file. ↩︎

  4. Using tools like Capybara and Selenium, this is perfectly do-able. But it's more complex to configure (especially when working with Docker images) and when using lots of Javascript, it is prone to timing errors. So to keep it simple, we'll just use simple HTTP response/request flows passing JSON back and forth. ↩︎

  5. However, I won't be using Rails "API mode" because I know that I will want to be adding a web user-interface later on. Ordinarily we wouldn't plan ahead like this, but it's easier to start with the full Rails stack rather than start with the cut-down API stack and add it all in later. ↩︎

  6. Unless they have some complaint about how it works ↩︎

  7. Beyond a bit of brainstorming. We find it difficult to throw away code that we've spent days working on. But it's easy to throw away a load of scribbled words on a whiteboard. ↩︎

  8. The conversation we had with the customer ↩︎

  9. "Isn't that obvious?" and "I just took it for granted that it would do that" are very common responses - remember, these people are not developers and do not understand how software works. ↩︎

  10. This is why working this way results in less code - you only end up implementing the stuff you really need ↩︎

  11. I'm using a Rails enum on the User model - enum status: {standard: 0, admin: 1, inactive: -1}. This means users can have a status of "standard", "admin" or "inactive" and Rails automatically defines scopes and helper methods for each of those statuses (and only uses an int field to store them in the database). ↩︎

  12. A doorkeeper application is a record in your server application that stores the ID and secret for the client that wants to connect ↩︎

  13. Authorize? Authorise? I'm English and you wouldn't believe the number of times I get my ruby constructors wrong by writing initialise instead of initialize ↩︎

Rahoul Baruah

Rahoul Baruah

Rubyist since 1.8.6. I like hair, dogs and Kim/Charli/Poppy. Also CTO at Collabor8Online.
Leeds, England