jekuno / milia

Easy multi-tenanting for Rails5 (or Rails4) + Devise
MIT License
341 stars 72 forks source link
devise gem milia multi-tenanting rails ruby

Build Status Dependency Status

milia

Milia is a multi-tenanting gem for Ruby on Rails applications. Milia supports Devise.

You are viewing the documentation for using milia with Rails 5.x applications.
If you want to use Rails 4.2.x instead please switch to the Rails 4.x branch.

Intro

Milia highlights

Basic concepts

Tenants == Organizations with Users / Members

A tenant is an organization with many members (users). Initially a user creates a new organization (tenant) and becomes its first member (and usually admin). Then he invites further members who can then login and join the tenant. Milia ensures that users can only access data of their own tenant (organization).

Tenanted models

Models which belong to a certain tenant (organization).
Add acts_as_tenant to the model body to activate tenanting for this model.
Most of your tables (except for pure join tables, users, and tenants) should be tenanted. Every record of a tenanted table needs to have a tenant_id set. Milia takes care of this.

Universal models

Models which aren't specific to a tenant (organization) but have system wide relevance. Add acts_as_universal to the model body to mark them as universal models.
Universal tables never contain critical user/company information. The devise user table must be universal and should only contain email, encrypted password, and devise-required data. All other user data (name, phone, address, etc) should be broken out into a tenanted table called members (Member belongs_to :user, User has_one :member). The same applies for organization (account or company) information. A record of a universal table must have tenant_id set to nil. Milia takes care of this.

Join tables

Pure join tables (has_and_belongs_to_many HABTM associations) are neither Universal nor Tenanted.

Tutorials + Documentation

Sample app

You can get a sample app up and running yourself using an easy, interactive RailsApp generator and an according Milia generator. If desired the generator can also prepare everything for you to push your app to Heroku. The sample app uses devise with the invite_member capability (and optionally recaptcha for new account sign-ups). It creates skeleton user, tenant and member models.

Simply follow the following steps:

mkdir milia-sample-app
cd milia-sample-app
rvm use ruby-2.3.1@milia-sample-app --ruby-version --create
gem install rails
rails new . -m https://raw.github.com/RailsApps/rails-composer/master/composer.rb

An interactive setup starts which asks you some questions.

After the setup finished add to your Gemfile:
gem 'milia'

Install milia: bundle install

In app/controllers/application_controller.rb add the following line immediately after protect_from_forgery:
before_action :authenticate_tenant!

Run the following commands:

spring stop
rails g milia:install --org_email='mail@your-provider.de' --skip_devise_generators=true

Setup the database: rake db:drop db:create db:migrate

Start the server: rails server

Open http://127.0.0.1:3000/users/sign_up in your browser. You're ready to go!

Previous sample app

For your reference: An outdated milia+devise sample app can be found at https://github.com/dsaronin/sample-milia-app and is live on Heroku: http://sample-milia.herokuapp.com
The according instructions on how to generate this sample app can be found at doc/sample.sh.

There are also outdated step-by-step instructions for setting this sample app up manually at doc/manual_sample.sh.

Installation

Adding milia to a new application

The quickest way: Follow the simple instructions of the chapter Sample App to generate a new app which uses devise+milia.

Add milia to an existing application

The recommended way to add multi-tenanting with milia to an existing app is to bring up the Sample App, get it working and then graft your app onto it. This ensures that the Rails+Devise setup works correctly.

Go step by Step

Don't try to change everything at once! Don't be a perfectionist and try to bring up a fully written app at once!

Just follow the instructions for creating the sample, exactly, step-by-step. Get the basics working. Then change, adapt, and spice to taste.

Bare minimal manual setup

(If you generated a [Sample App](Sample App) all of the following steps have been done already.)

Add to your Gemfile:

  gem 'milia', '~>1.3'

Then run the milia generator:

  $ bundle install
  $ rails g milia:install --org_email='<your smtp email for dev work>'

Note: The milia generator has an option to specify an email address to be used for sending emails for confirmation and account activation.

For an in depth explanation of what the generator does have a look at README_DETAILS.

Make any changes required to the generated migrations, then:

  $ rake db:create
  $ rake db:migrate

Application controller

app/controllers/application_controller.rb add the following line IMMEDIATELY AFTER line 4 protect_from_forgery

  before_action :authenticate_tenant!   # authenticate user and sets up tenant

  rescue_from ::Milia::Control::MaxTenantExceeded,   :with => :max_tenants
  rescue_from ::Milia::Control::InvalidTenantAccess, :with => :invalid_tenant

Setup base models

Generate the tenant migration

  $ rails g model tenant tenant:references name:string:index

Generate the tenants_users join table migration

  $ rails g migration CreateTenantsUsersJoinTable tenants users

EDIT: db/migrate/20131119092046_create_tenants_users_join_table.rb then uncomment the first index line as follows: t.index [:tenant_id, :user_id]

ALL models require a tenanting field, whether they are to be universal or to be tenanted. So make sure you have migrations for all models which add the following:

db/migrate/xxxxxxx_create_model_xyz.rb

  t.references :tenant

Tenanted models also require indexes for the tenant field.

  add_index :<tablename>, :tenant_id

BUT: Do not add any belongs_to :tenant statements into any of your models. milia will do that for all. However it makes sense to add into your app/models/tenant.rb file one line per tenanted model such as the following (replacing with your model's name):

  has_many  :<model>s, dependent: :destroy

The reason for this is that if you wish to have a master destroy tenant action, it will also remove all related tenanted tables and records automatically.

Do NOT add a reference to the user model such as

  has_many  :users, dependent: :destroy

because it produces errors.

Designate which model determines the account

Add the following actsas... to designate which model will be used as the key into tenants_users to find the tenant for a given user. Only designate one model in this manner e.g.:

app/models/user.rb

  class User < ActiveRecord::Base

    acts_as_universal_and_determines_account

  end
Designate which model determines the tenant

Add acts_as_universal_and_determines_tenant to designate which model will be used as the tenant model. It is this id field which designates the tenant for an entire group of users which exist within a single tenanted domain. Only designate one model in this manner.

app/models/tenant.rb

  class Tenant < ActiveRecord::Base

    acts_as_universal_and_determines_tenant

  end 
Clean up tenant references

Clean up any generated belongs_to tenant references in all models which the generator might have generated (both acts_as_tenant and acts_as_universal).

Setup your custom models

Designate tenanted models

Add acts_as_tenant to ALL models which are to be tenanted. Example for a Post model:

app/models/post.rb

  class Post < ActiveRecord::Base

    acts_as_tenant

  end
Designate universal models

Add acts_as_universal to ALL models which are to be universal.

Role based authorization

You can use any role based authorization you like, e.g. the rolify gem with cancancan, authority or pundit.

Milia API Reference Manual

Get current tenant

From models call Tenant.current_tenant or Tenant.current_tenant_id to get the current tenant.

Change current tenant

Call set_current_tenant( tenant_id ) from controllers. (for example, if a member can belong to multiple tenants and wants to switch between them). NOTE: you will normally NEVER do this manually at the beginning of a session. Milia does this automatically during authorize_tenant!.

From background job, migration, rake task or console you can use Tenant.set_current_tenant(tenant). tenant can either be a tenant object or an integer tenant_id; anything else will raise an exception.

Use with caution! Normally tenants should never be changed from within models. It is only useful and safe when performed at the start of a background job (DelayedJob#perform), rake task, migration or start of rails console.

Iterate over tenants

To iterate over all instances of a certain model for all tenants do the following:

Tenant.find_each do |tenant|
  Tenant.set_current_tenant(tenant)
  Animal.update_all alive: true
end

Rails Console

Note that even when running the console, (rails console) it will be run in multi-tenanting mode. Call Tenant.set_current_tenant(tenant_id) accordingly.

Milia callbacks

In some applications, you will want to set up commonly used variables used throughout your application, after a user and a tenant have been established and authenticated. This is optional and if the callback is missing, nothing will happen.

app/controllers/application_controller.rb

  def callback_authenticate_tenant
    # set_environment or whatever else you need for each valid session
  end

Security / Caution

Contributing to milia

Testing milia

For instructions on how to run and write tests for milia please consider the README for testing

Changelog

See CHANGELOG.md for changes and upgrade instructions.

License

See LICENSE.txt for further details.