6am is our blog about developing Roll Call

our web-app for businesses that revolve around talent, teams and projects.

We hope this blog will be about launching Roll Call very soon.

Adding multi-tenancy to your Rails app: acts_as_tenant Erwin Oct 03

Roll Call is implemented as a multi-tenant application: each user gets their own instance of the app, content is strictly scoped to a user’s instance. In Rails, this can be achieved in various ways. Guy Naor did a great job of diving into the pros and cons of each option in his 2009 Acts As Conference talk. If you are doing multi-tenancy in Rails, you should watch his video.

With a multi-db or multi-schema approach, you only deal with the multi-tenancy-aspect in a few specific spots in your app (Jerod Santo recently wrote an excellent post on implementing a multi-schema strategy). Compared to the previous two strategies, a shared database strategy has the downside that the ‘multi-tenancy’-logic is something you need to actively be aware of and manage in almost every part of your app.

Using a Shared Database strategy is alot of work!

For various other reasons we opted for a shared database strategy. However, for us the prospect of dealing with the multi-tenancy-logic throughout our app, was not appealing. Worse, we run the risk of accidently exposing content of one tenant to another one, if we mismanage this logic. While researching this topic I noticed there are no real ready made solutions available that get you on your way, Ryan Sonnek wrote his ‘multitenant’ gem and Mark Connel did the same. Neither of these solution seemed “finished” to us. So, we wrote our own implementation.

First, how does multi-tenancy with a shared database strategy work

A shared database strategy manages the multi-tenancy-logic through Rails associations. A tenant is represented by an object, for example an Account. All other objects are associated with a tenant: belongs_to :account. Each request starts with finding the @current_account. After that, each find is scoped through the tenant object: current_account.projects.all. This has to be remembered everywhere: in model method declarations and in controller actions. Otherwise, you’re exposing content of other tenants.

In addition, you have to actively babysit other parts of your app: validates_uniqueness_of requires you to scope it to the current tenant. You also have to protect agaist all sorts of form-injections that could allow one tenant to gain access or temper with the content of another tenant (see Paul Gallaghers presentation for more on these dangers).

Enter acts_as_tenant

I wanted to implement all the concerns above in an easy to manage, out of the way fashion. We should be able to add a single declaration to our model and that should implement:

  1. scoping all searches to the current Account
  2. scoping the uniqueness validator to the current Account
  3. protecting against various nastiness trying to circumvent the scoping.

The result is acts_as_tenant (github), a rails gem that will add multi tenancy using a shared database to your rails app in an out-of-your way fashion.

In the README, you will find more information on using acts_as_tenant in your projects, so we’ll give you a high-level overview here. Let’s suppose that you have an app to which you want to add multi tenancy, tenants are represented by the Account model and Project is one of the models that should be scoped by tenant:

1
2
3
4
5
6
7
8
9
class Addaccounttoproject < ActiveRecord::Migration
  def up
    add_column :projects, :account_id, :integer
  end

class Project < ActiveRecord::Base
  acts_as_tenant(:account)
  validates_uniqueness_to_tenant :name
end

What does adding these two methods accomplish:

  1. it ensures every search on the project model will be scoped to the current tenant,
  2. it adds validation for every association confirming the associated object does indeed belong to the current tenant,
  3. it validates the uniqueness of :name to the current tenant,
  4. it implements a bunch of safeguards preventing all kinds of nastiness from exposing other tenants data (mainly form-injection attacks).

Ofcourse, all the above assumes acts_as_tenant actually knows who the current tenant is. Two strategies are implemented to help with this.

Using the subdomain to workout the current tenant

1
2
3
4
class ApplicationController < ActionController::Base
   
   set_current_tenant_by_subdomain(:account, :subdomain)
end

Adding the above methods to your application controller tells acts_as_tenant that

  1. the current tenant should be found based on the subdomain (e.g. account1.myappdomain.com),
  2. tenants are respresented by the Account model and
  3. the Account model has a column named subdomain that should be used the lookup the current account, using the current subdomain.

Passing the current account to acts_as_tenant yourself

1
2
3
4
5
class ApplicationController < ActionController::Base

  current_account = Account.method_to_find_the_current_account
  set_current_tenant_to(current_account)
end

Acts_as_tenant also adds a handy helper to your controllers current_tenant, containing the current tenant object.

Great! Anything else I should know? A few caveats:

  • scoping of models only works if acts_as_tenant has a current_tenant available. If you do not set one by one of the methods described above, no scope will be applied!
  • for validating uniqueness within a tenant scope you must use the validates_uniqueness_to_tenant method. This method takes all the options the regular validates_uniqueness_of method takes.
  • it’s probably best to add the acts_as_tenant declaration after any other default_scope declarations you add to a model (i’m not exactly sure how rails 3 handles the chaining. If someone can enlighten me, thanks!).

We have been testing acts_as_tenant within Roll Call during recent weeks and it seems to be behaving well. Having said that, we welcome any feedback. This is my first real attempt at a plugin and the possibility of various improvements is almost a given.

Next post i’ll describe how we let this all play nice with other gems such as Devise and acts_as_taggable.

6 am - Wake Up! Erwin Sep 26

With this post, I have the honor of launching our blog, 6am. At the same time we have also put up an announcement page, announcing Roll Call.

We are excited that, over the coming weeks, we can start sharing the results of our work. Roll Call is a web-app for businesses with talented teams of professionals that work on client projects.

In addition, I will add some tidbits from the development process here as well, as developing Roll Call has turned out to be a very educational experience for all of us. In fact, I am working on such a post right now.

T7S82J7E4QRR