Thursday, March 3, 2011

Multitenant Ruby Gem


Multitenant: locking down your app and making cross tenant data leaks a thing of the past...since 2011

Designing multi-tenant applications has increasingly becoming the norm for software developers. The benefits of building an application with a multi-tenant architecture are almost too many to list (cost savings, data mining, simplified operations, etc). But, let there be no doubt that building a multi-tenant application increases the overall complexity of the application.

The Multitenant gem helps alleviate that complexity and ensures that all SQL queries executed adhere to the scoping of the current tenant. It works with any Rails3 compatible application without any modifications to your existing schema.

To configure, add the belongs_to_multitenant declaration to any model that has an association to a particular tenant.  This will inject a new SQL scope into your model.
class User < ActiveRecord::Base
  belongs_to :tenant
  belongs_to_multitenant
end

Now, all queries executed within the Multitenant.with_tenant block will automatically be scoped to the current tenant. No longer must you worry about a stray MyModel.all call exposing data to the wrong users! Also, any new records created within this block will automatically be associated to the current tenant. Another win for ensuring data separation!
# use an Rails around_filter to setup this block
Multitenant.with_tenant current_tenant do
  # queries within this block are automatically
  # scoped to the current tenant
  User.all

  # records created within this block are
  # automatically assigned to the current tenant
  User.create :name => 'Bob'
end

Unintentionally leaking data between tenants should be the NUMBER ONE concern when building multi-tenant apps and it surprises me how few resources are available to assist developers in building multi-tenant apps and ensuring data separation between tenants. So, if you're building a multi-tenant Rails app, make sure the Multitenant gem is in your toolbox!

Source code is 100% opensource and available on Github:
https://github.com/wireframe/multitenant

Gem is available on rubygems:
https://rubygems.org/gems/multitenant

11 comments:

  1. What are the advantages of your approach over the approach suggested by Guy Naor in acts_as_conference 2009? (Google for the Confreaks video of his talk.) Thanks

    ReplyDelete
  2. Awesome! Thank you! This will save me so much time! I look forward to trying it out. =]

    ReplyDelete
  3. I've noticed that you're using the DynamicDefaultScoping to create the scoping. Doesn't that mean that the belongs_to_multitenant call should be placed after all other scopes?

    ReplyDelete
  4. ryan, good work and i agree - there is a surprising lack of multitenant support in the Rails community considering how popular this approach is becoming so you're on the money here! can i ask, how would you implement your code alongside the Rails3 subdomain helper? e.g. http://area1.mydomain.com and http://area2.mydomain.com such that the subdomain is the tenant (doesn't require any authentication as it is just a hop between areas and thus content for those areas). i'd be happy to support your work here with a little contribution if you could help? thanks in advance.

    ReplyDelete
  5. I tried using your gem, getting an error:
    undefined method `foreign_key' for nil:NilClass
    Any pointers on what could be going wrong?
    my code:
    + i have added an around_filter :
    Multitenant.with_tenant my_tenant_obj do
    yield
    end
    + added in model
    belongs_to :ModelName
    belongs_to_multitenant

    ReplyDelete
  6. I am using MYSQL for my Ruby web application and I want to use the option of one DB per tenant, can anyone share any sample or tutorial to achieve the same?

    ReplyDelete
  7. I am using MYSQL for my Ruby web application and I want to use the option of one DB per tenant, can anyone share any sample or tutorial to achieve the same?

    ReplyDelete
  8. @kartik using separate databases per tenant is called "sharding" and is a very different approach than multi-tenancy.

    there are plenty of good docs and tutorials across the web to discuss sharding if you are pursue that approach.

    ReplyDelete
  9. Hi Ryan,

    I'm wondering how your gem handles situations where two or more users are requesting data at the same time? Seems your managing state in the the Multitenant constant, using class attr_accessor methods. If that's the case, is it possible that the @current_tenant could get swapped, resulting in a tenant data leak?

    Thanks,
    Nathan

    ReplyDelete
  10. Hi Ryan,

    I'm wondering how your gem handles situations where two or more users are requesting data at the same time? Seems your managing state in the the Multitenant constant, using class attr_accessor methods. If that's the case, is it possible that the @current_tenant could get swapped, resulting in a tenant data leak?

    Thanks,
    Nathan

    ReplyDelete
  11. @Nathan

    most rails applications are thread safe so cross thread data access is not an issue.

    if multithread access is required, storing the current_tenant in a thread local variable would be the way to go.

    patches are always welcome!

    ReplyDelete