https://github.com/salsify/rails-multitenant
https://github.com/salsify/rails-multitenant
gem
Last synced: about 1 year ago
JSON representation
- Host: GitHub
- URL: https://github.com/salsify/rails-multitenant
- Owner: salsify
- License: mit
- Created: 2015-12-22T15:44:12.000Z (over 10 years ago)
- Default Branch: master
- Last Pushed: 2024-11-08T17:30:12.000Z (over 1 year ago)
- Last Synced: 2025-05-26T12:19:20.989Z (about 1 year ago)
- Topics: gem
- Language: Ruby
- Homepage:
- Size: 6.53 MB
- Stars: 41
- Watchers: 61
- Forks: 0
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE.txt
- Code of conduct: CODE_OF_CONDUCT.md
- Codeowners: .github/CODEOWNERS
Awesome Lists containing this project
README
# RailsMultitenant
[][gem]
[][circleci]
[gem]: https://rubygems.org/gems/rails_multitenant
[circleci]: https://circleci.com/gh/salsify/rails-multitenant
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:
```ruby
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:
```ruby
class Product < ActiveRecord::Base
include RailsMultitenant::MultitenantModel
multitenant_on :organization_id
end
```
The model can then be used as follows:
```ruby
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:
```ruby
class Product < ActiveRecord::Base
include RailsMultitenant::MultitenantModel
multitenant_on_model :organization
end
```
The model can then be used as follows:
```ruby
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:
```ruby
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.
```ruby
class MyClass
include RailsMultitenant::GlobalContextRegistry::CurrentInstance
end
MyClass.current_id = 123
MyClass.current # => #
```
#### Dependency Tracking
For classes that are dependent on other `Current` classes you can register dependencies.
```ruby
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.
```ruby
klass = MyClass.new
MyClass.current = klass
DependentClass.current # => #
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](https://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](contributor-covenant.org) code of conduct.