Zero to Multitenant in 15 Minutes – A Rails Walkthrough

2013-audi-brand-general-159

In this walk-thru, I will show you how to use rails and apartment to create a multitenant app that uses the domain name to determine which tenant to present with very little code.  You will see that adding additional functionaltiy to all tenants at once is as easy as developing any other Rails app.

One Code Base – Many Customers

It’s what all the cool kids are doing…

From Wikipedia:

Software Multitenancy refers to a software architecture in which a single instance of a software runs on a server and serves multiple tenants. A tenant is a group of users who share a common access with specific privileges to the software instance. With a multitenant architecture, a software application is designed to provide every tenant a dedicated share of the instance including its data, configuration, user management, tenant individual functionality and non-functional properties. Multitenancy contrasts with multi-instance architectures, where separate software instances operate on behalf of different tenants.

Multenant apps are all around us – github, Pivotal Tracker, Amazon Web Services, Salesforce.com, and the list goes on.

Gains & Pains

If you find yourself writing an app that could apply to multiple customers, here are some gains you would get by using a multitenant architecture:

Gains

  • One click “deploy” for new customers
  • Cleaner models – avoids scoping every model to the tenant
  • Data mining
  • Fix it for one customer, they all get the fix
  • One deployment to manage
  • One database to back up

Pains?

Nothing is free – here are some things to watch out for:

  • One database schema – all tenants need to use the same database structure.
  • Release Management – changing code for one tenant changes ’em all
  • Customization per tenant has to be “built in” via per tenant settings
  • QOS – one goes down, they all go down

Enough talk – On with the show

Where we’re headed

We are going to build a simple app that is applicable to automobile companies. This app will allow customers to schedule an appointment at one of the company’s repair facilities. Since there are many automobile companies who all share the need to get their customers scheduled, a multitenant app is perfect for the job.

We will have one shared Tenant model which will keep track of each of the automobile companies. Their cusomers would visit a unique URL for the company and see a branded website for that company. We will use Audi and Tesla as example automobile companies in this app, and we’ll use www.audiservice.com and www.teslaservice.com as the URLs.

For each tenant, we will have 3 models to represent their Customers, Shops, and vehicle Models. The schema for each of these models will be shared by all tenants, but each tenant will have their own data per model.

Our Models

auto_uml_graffle

There are multiple ways to handle this shared schema/separate data situation. Depending on the underlying database you are using, the apartment gem supports having a separate database per tenant as well as a more efficient single database with multiple schemas. We will use the latter here using postgresql, which has the side benefit of working with Heroku deployments if that’s your thing.

The User Experience

For this sample app, we are going to show the user a different website for each tenant. Thus, when they visit www.audiservice.com, they will see an Audi branded website, but when they visit www.teslaservice.com, they will see a Tesla site. Since our focus is on the multitenant implementation, we will leave the finer details of a nicely branded website to you for extra credit. We will, however, have a different background image and welcome message for each tenant.

Let’s Start Coding

Spoiler alert – the completed sample app is available on my github repo.

Start with a fresh rails app and add some gems

# Build the rails app
rails new --database=postgresql --skip-bundle --skip-turbolinks --skip-test-unit auto_repairs
cd auto_repairs

# Be sure the database exists - postgres command here
createdb auto_repairs_development

Add the following to the Gemfile:

gem 'apartment'                     # Multitenant support
gem 'hirb'                          # Better DB output in IRB
gem 'simple_form'                   # Easier form gereration
gem 'bootstrap-sass'                # Sass-powered version of Bootstrap, ready to drop right into your Sass powered applications

The big one there is apartment, which we’ll get to shortly. The others are hirb to beautify database tables in the rails console, simple_form to tame form syntax, and bootstrap-sass to make the form look better and provide us with a much needed CSS framework.

Once added, let’s get them installed:

bundle install
bundle exec rails g simple_form:install --bootstrap
bundle exec rails g apartment:install

Getting Simple Form and Bootstrap Configured

The simple_form install just sets the default formatting and stuff up so it leverages bootstrap and ties nicely into rails. There are some great posts on that elsewhere.

The apartment:install generator creates apartment.rb, which we’ll walk through shortly.

But first, we have to put some boiler plate code in for bootstrap-sass:

First, rename application.css to application.scss so we can import boostratp.

Next, edit app/assets/stylesheets/application.scss to look like this:

/*
 *= require_tree .
 *= require_self
 */
@import "bootstrap-sprockets";
@import "bootstrap";

And edit app/assets/javascripts/application.js to look like this:

//= require jquery
//= require bootstrap
//= require jquery_ujs
//= require_tree .

Apartment Setup – finally!

We need to tell apartment about our Tenant model and how we want it to determine which is the current tenant when someone visits our website.

First, let’s build the tenant model:

bundle exec rails g model Tenant name domain image_url

Now, edit config/initializers/apartment.rb to look like this:

require 'apartment/elevators/domain'
Apartment.configure do |config|
  config.excluded_models = %w{ Tenant }
  config.tenant_names = lambda { Tenant.pluck :domain }
end
Rails.application.config.middleware.use 'Apartment::Elevators::Domain'

Feel free to leave all of the comments in there for later when you want to learn more. For now, I’ll describe what we have.

Apartment has elevators that determine how to choose which tenant the user sees. We’re using the domain elevator, which strips off the www. and .com from the url, just leaving the domain. In our case, something like audiservice. The first and last lines take care of enabling the domain elevator. There are others you could use, like subdomain, and you can even write your own if you want to get fancy or have more particular requirements for what constitutes a different tenant, like a URL path name.

We then tell apartment to exclude the Tenant model from the tenants – that means it will be shared among all tenants, usable by all instances of the app.

Next, we provide a lambda function that returns all of the possible tenant names (like audiservice and teslaservice) that apartment will use to find the database or schema for each tenant.

The Tenant model

Edit app/models/tenant.rb to look like this:

class Tenant < ActiveRecord::Base
  after_create :add_tenant_to_apartment

  def self.current
    tenant = Tenant.find_by domain:Apartment::Tenant.current
    raise ::Apartment::TenantNotFound, "Unable to find tenant" unless tenant
    tenant
  end

  def switch!
    Apartment::Tenant.switch! domain
  end

  private
    def add_tenant_to_apartment
      Apartment::Tenant.create(domain)
    end

    def drop_tenant_from_apartment
      Apartment::Tenant.drop(domain)
    end
end

There’s some cool stuff in there. When you want to add a new tenant to your app, just create a new record in the Tenant database. The after_create calls our private add_tenant_to_apartment method which adds a new schema to the database, effectively giving our tenant its own set of tenant-specific tables.

self.current lets us use Tenant.current within our app to easily get at the current tenant to figure out stuff like the company name or logo so we can properly brand the website.

switch! gives us a very convenient way to switch the entire ActiveRecord world to point to our tenant. After switching, all database queries will be done on tables for that tenant.

We’ll see examples of using these methods after we get the rest set up.

Now for the stuff you already know

The point of the apartment gem is to let you code most of the app as if you were just doing a single-tenant app. In this section, we’re going to do just that for the auto repair facility app.

First, build the models:

bundle exec rails g model Model name
bundle exec rails g model Shop name address
bundle exec rails g scaffold Customer name phone email model:references shop:references
# don't forget to migrate
bundle exec rake db:migrate

The root path will just create a new customer and schedule an appointment. Ultimately, we’d have more models so customers could come back and schedule another appointment, etc., but this blog is about apartment for corn sake.
Edit config/routes/routes.rb to look like this:

Rails.application.routes.draw do
  resources :customers
  root 'customers#new'
end

Add some per-tenant branding

We’ll access the current tenant’s background_image_url in the main layout.

Edit app/helpers/application_helper.rb to look like this:

module ApplicationHelper
  def background_image_url
    Tenant.current.image_url
  end
end

Edit app/views/layouts/application.html.erb by replacing the line with:

<body style = "background-image:url('<%= background_image_url %>'); background-size:cover">

We’re going to use a nice modal dialog effect from bootstrap to get the customer’s details in the new action.

Edit app/views/customers/new.html.erb to look like this:

<%= simple_form_for(@customer) do |f| %>
  <div class="modal fade" id="myModal">
    <div class="modal-dialog">
      <div class="modal-content">
        <div class="modal-header">
          <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
          <h3 class="modal-title">Welcome to <%= Tenant.current.name%></h3>
          <h5>Please sign in and choose your repair facility</h5>
        </div>
        <div class="modal-body">
          <%= f.error_notification %>
          <div class="form-inputs">
            <%= f.input :name %>
            <%= f.input :phone %>
            <%= f.input :email %>
            <%= f.association :model %>
            <%= f.association :shop %>
          </div>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
          <%= f.button :submit, "Schedule My Appointment!", class:%w(btn btn-success) %>
        </div>
      </div>
    </div>
  </div>
<% end %>

<script type="text/javascript">
    $(window).load(function(){
        $('#myModal').modal('show');
    });
</script>

When the user has submitted the form, we just want to say thanks. Edit app/views/customers/show.html.erb to look like this:

<div class='panel panel-default text-center'>
  <h1>Thank you!</h1>
  <h2>We will contact you soon with your appointment.</h2>
  <a href="/">Home</a>
</div>

Sample Data

Now that we have the app written, let’s get some sample data into the database and take her for a spin.

bundle exec rails console

Run the following commands in the console.

First, we will create our two example tenants. Take a look at the output to see that there is quite a bit going on here. Apartment is setting up the other tables when for each tenant.

# Create two tenants
Tenant.create name:"Audi International", domain:"audiservice", image_url:"http://www.autocarpro.in/IMG/314/10314/madurai-audi.jpg"
Tenant.create name:"Tesla Motors", domain:"teslaservice", image_url:"https://upload.wikimedia.org/wikipedia/commons/0/09/Tesla_Munich_store.jpg"

Now that we have the tenants set up, let’s switch to each one and add tenant-speific data:

Tenant.find_by(domain:"audiservice").switch!
models = %w(A3 A4 A5 R8 Q5 A6 Q7 Q3 A8 TT A5 RS7 A7 S8 S5 S3 SQ5 S4 RS5 S7 S6 Allroad TTS Q5Hybrid A3e-tron)
models.each{|n| Model.create name:n}
Shop.create name:"Bob's Audi Repair Shop", address:"123 E Main Street, Mesa, AZ"
Shop.create name:"Dingaling Dent Doctor", address:"42 E Lafayette Avenue, Anytown, USA"

Tenant.find_by(domain:"teslaservice").switch!
models = %w(S X)
models.each{|n| Model.create name:n}
Shop.create name:"Sam's Tesla Recovery Studio", address:"789 N Pole Street, Sitka, AK"
Shop.create name:"Tesla by Tom", address:"23141 Rt. 7, Anytown, USA"

/etc/hosts?!

Normally, we’d start up a rails server rails s and visit localhost:3000 in a browser. But because we’re in multitenant land, we need to tell apartment which tenant to use using the domain name. Since Audi and Tesla are not likely to give us access to their DNS servers, we need to fake it.

The easiest way to do this on unix-like environments is to edit /etc/hosts. You will need administrator privileges on your development machine to do this. If you cannot do this, you could pick domain names you do control and temporarily set up DNS to point to 127.0.0.1.

Add the following to /etc/hosts:

127.0.0.1    www.audiservice.com
127.0.0.1    www.teslaservice.com

Try it out

bundle exec rails s

Visit http://www.audiservice.com:3000 in your browser and you should see the Audi branded site.

Next, visit http://www.teslaservice.com:3000 in your browser and you should see the Tesla branded site.

Add a few appointments by typing some names and stuff. We’re not doing any validation, so you can type as much or little as you want. Just put some stuff in there so we can look at the data.

Checking out the data

Let’s open up a rails console and see what we did.

bundle exec rails console
# Enable hirb so the database tables are more readable
Hirb.enable

# Let's see our two tenants
Tenant.all

# Let's see what our first tenant has going on
Tenant.first.switch!
Customer.all
Shop.all
Model.all

# How about the other tenant
Tenant.last.switch!
Customer.all
Shop.all
Model.all

Cool! Each tenant has its own data. We can write it once and sell it all over the place! I hope you had fun and got some worthwile help building a multitenant website. Leave a comment below if you are in the mood – I’d enjoy hearing what you think of this blog, multitenancy, rails, or a good limerick.

Cheers!

10 Comments


  1. Hey!

    Awesome write up. What would you recommend for having multitenancy that would have random numbers or the tenant name in the url ie. app.example.com/2892/projects… or app.example.com/urbanhats/projects…

    Thanks!

    Reply

  2. Between this post and the GoRails.com video on Apartment… I’ve nailed down what I’m using for multitenancy and more importantly… How to use it.

    THANK YOU!!!

    Reply

  3. How should I configure DNS for multitenat app???

    Reply

    1. Once you have the elevator set up in apartment to respond to a specific domain name – e.g. ‘www.myapp.com’, point ‘www.myapp.com’ to the server where you’re hosting your multitenant rails app. If you have a fixed IP address for your rails app, you can use an “A” record to point ‘www.myapp.com’ to that IP address. If you already have a hostname for your rails server, like ‘railsapps.mydomain.com’ , you can use a CNAME record pointing ‘www.myapp.com’ to ‘railsapps.mydomain.com’.

      Reply

  4. Thanks for the great tutorial, want to learn multi tenancy setup for quite a while. Am able to follow it in one go and get it running, your clear guide surely is a big help 🙂

    Reply

  5. Great Tutorial! Thanks!

    I have one question. The ‘domain’ elevator strips off the www. and .com from the url, just leaving the domain, but, if I have http://www.myapp.com and http://www.myapp.net, each one for different users, how can I get the entire request.host instead of only the domain?

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *