Skip to content

salsify/rails-multitenant

Repository files navigation

RailsMultitenant

Gem Version Build Status

rails_multitenant is a gem for isolating ActiveRecord models from different tenants. The gem assumes tables storing multi-tenant models include an appropriate tenant id column.

Installation

Add this line to your application's Gemfile:

gem 'rails_multitenant'

And then execute:

$ bundle

Or install it yourself as:

$ gem install rails_multitenant

If you're using Rails, there's nothing else you need to do.

Otherwise, you need to insert RailsMultitenant::Middleware::IsolatedContextRegistry into your middleware stack

Usage

The gem supports two multi-tenancy strategies:

  1. Based on a model attribute, typically a foreign key to an entity owned by another service
  2. Based on a model association

The gem uses ActiveRecord default scopes to make isolating tenants fairly transparent.

Multi-tenancy Based on Model Attributes

The following model is multi-tenant based on an organization_id attribute:

class Product < ActiveRecord::Base
  include RailsMultitenant::MultitenantModel

  multitenant_on :organization_id
end

The model can then be used as follows:

RailsMultitenant::GlobalContextRegistry[:organization_id] = 'my-org'

# Only returns products from 'my-org'
Product.all

# Returns products across all orgs
Product.strip_organization_scope.all

# Or set the current organization in block form
RailsMultitenant::GlobalContextRegistry.with_isolated_registry(organization_id: 'my-org') do
  # Only returns products from 'my-org'
  Product.all
end

By default this adds an ActiveRecord validation to ensure the multi-tenant attribute is present but this can be disabled by passing required: false to multitenant_on.

Multi-tenancy Based on Associated Models

The following model is multi-tenant based on an Organization model:

class Product < ActiveRecord::Base
  include RailsMultitenant::MultitenantModel

  multitenant_on_model :organization
end

The model can then be used as follows:

Organization.current_id = 1

# Only returns products from organization 1
Product.all

# Use the automatically generated belongs_to association to get
# a product's organization
Product.first.organization

# Or set the current organization in block form
Organization.as_current_id(1) do
  # Only returns products from organization 1
  Product.all
end

By default this adds an ActiveRecord validation to ensure the tenant model is present but this can be disabled by passing required: false to multitenant_on_model.

current Models

Classes can be enabled to have current, thread-local instances. For a standard class this is done with:

class MyClass
  include RailsMultitenant::GlobalContextRegistry::Current
end

MyClass.current = MyClass.new

For an ActiveRecord model you can use the following, which additionally allows storing the current model ID.

class MyClass
  include RailsMultitenant::GlobalContextRegistry::CurrentInstance
end

MyClass.current_id = 123
MyClass.current # => #<MyClass id: 123>

Dependency Tracking

For classes that are dependent on other Current classes you can register dependencies.

class DependentClass
  include RailsMultitenant::GlobalContextRegistry::Current
  provide_default :default
  global_context_dependent_on MyClass

  def self.default
    new(MyClass.current.dependent_id)
  end

  def initialize(id)
    @id = id
  end
end

When doing so, clearing the current class on the referenced class will also clear the current context of the dependent class.

klass = MyClass.new
MyClass.current = klass
DependentClass.current # => #<DependentClass id: klass.dependent_id>

MyClass.current = nil
DependentClass.current # => nil

For bi-directional dependencies you can use #global_context_mutually_dependent_on instead of #global_context_dependent_on.

Shorthand

When using rails-multitenant in a project, it is common to need to set values in RailsMultitenant::GlobalContextRegistry at the rails console.

This is difficult to type. Alternatively you can shorten it to RailsMultitenant. For example you might type RailsMultitenant[:organization_id] = 'some value' and it will have the same effect as the long version.

This is mainly intended as a console convenience. Using the long form in source code is fine, and more explicit.

Development

After checking out the repo, run bin/setup to install dependencies. Then, run rake false to run the tests. You can also run bin/console for an interactive prompt that will allow you to experiment.

To install this gem onto your local machine, run bundle exec rake install. To release a new version, update the version number in version.rb, and then run bundle exec rake release, which will create a git tag for the version, push git commits and tags, and push the .gem file to rubygems.org.

Contributing

Bug reports and pull requests are welcome on GitHub at https://github.com/salsify/rails-multitenant. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.

About

No description or website provided.

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Packages

No packages published

Contributors 13