Web app often faces multitenancy problem and there are already few gems that help to solve this issue but this one uses different approach. Instead of default scopes or creating a separate database for every single tenant this gem uses mysql views and triggers. The idea is taken from blog.empowercampaigns.com/post/1044240481/multi-tenant-data-and-mysql
The advantages of such approach:
-
no default scopes
-
only one db
-
no Threads
-
the biggest advantage is that responsebility about the data is moved to the db layer. It means the customer shouldn’t be warry about developer’s mistake before very critical demo and the logic of the app is much simplier.
-
<img src=“https://fury-badge.herokuapp.com/rb/multitenant-mysql.png” alt=“Gem Version” />
-
<img src=“https://travis-ci.org/activebridge/multitenant-mysql.png?branch=master”/>
-
<img src=“https://gemnasium.com/activebridge/multitenant-mysql.png” alt=“Dependency Status” />
-
<img src=“https://codeclimate.com/github/activebridge/multitenant-mysql.png” />
1 Add this line to your application’s Gemfile:
gem 'multitenant-mysql'
2 And then execute:
$ bundle
1 run
rails g multitenant:install
This will create a sample of config file in “rails_root/config/initializers/multitenant_mysql_conf.rb”. Update it according to your needs. E.g:
Multitenant::Mysql.configure do |conf| conf.models = ['Book', 'Task'] conf.tenants_bucket 'Subdomain' do |tb| tb.field = 'name' end end
Important: Before moving on you have to update this file as all further steps use those configs.
2 generate migrations based on configs
rails g multitenant:migrations
3 migrate the database
rake db:migrate
4 generate mysql views and triggers
rails g multitenant:views_and_triggers:create
5 in ApplicationController
set_current_tenant :tenant_method
where ‘:tenant_method` is a methods which produces the current tenant name. It can be subdomain or current user’s company name E.g.
class ApplicationController < ActionController::Base set_current_tenant :tenant_name def tenant_name current_user.tenant.name # or request.subdomain end end
if method used by ‘set_current_tenant` returns blank name then `root` account is used
-
if you have changed columns for tenant dependend models then you need to regenerate views, you could use generator
multitenant:views_and_triggers:refresh
-
if you want to use subdomain as a tenant name then you can use method
set_current_tenant_by_subdomain
-
list of available generators to manage views and triggers
multitenant:triggers:create multitenant:triggers:drop multitenant:triggers:refresh multitenant:views:create multitenant:views:drop multitenant:views:refresh multitenant:views_and_triggers:create multitenant:views_and_triggers:drop multitenant:views_and_triggers:refresh
About the main principles you can read here blog.empowercampaigns.com/post/1044240481/multi-tenant-data-and-mysql.
As for gem implementation. There are three main things to make it work:
-
models that are tenant dependend This one should be pretty obvious, all you need to do is just specify models in config file, no code inside the model;
-
model which stores all tenants (in this case this is just a username of mysql account) The information about this one you need to provide in config file. This one is very simple, all you need is a column like ‘name` or `title` (or you can specify in config file) and by creating new entry you create MySql account (new tenant). Only after creating new entry with particular name you are able to use new tenant (otherwise there is just no MySql account and it won’t work for particular tenant)
-
‘set_current_tenant` in ApplicationController As a param of this method is the name of ApplicationController method which returns the name of current tenant. Behind the scenes it creates a before_filter which uses current tenant name to establish appropriate connection to db.
So far it is tested with ruby 1.9.3 and rails 3.2 If you get any problems please feel free to post an issue
-
Fork it
-
Create your feature branch (‘git checkout -b my-new-feature`)
-
Commit your changes (‘git commit -am ’Added some feature’‘)
-
Push to the branch (‘git push origin my-new-feature`)
-
Create new Pull Request