rails_multitenant 0.11.0 → 0.15.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/LICENSE.txt +21 -0
- data/lib/rails_multitenant.rb +7 -4
- data/lib/rails_multitenant/global_context_registry.rb +43 -198
- data/lib/rails_multitenant/global_context_registry/current.rb +98 -0
- data/lib/rails_multitenant/global_context_registry/current_instance.rb +103 -0
- data/lib/rails_multitenant/global_context_registry/registry_dependent_on.rb +13 -0
- data/lib/rails_multitenant/middleware/extensions.rb +3 -3
- data/lib/rails_multitenant/middleware/isolated_context_registry.rb +2 -0
- data/lib/rails_multitenant/middleware/railtie.rb +3 -1
- data/lib/rails_multitenant/multitenant_model.rb +6 -2
- data/lib/rails_multitenant/rspec.rb +2 -0
- data/lib/rails_multitenant/version.rb +3 -1
- metadata +69 -76
- data/.gitignore +0 -14
- data/.rspec +0 -3
- data/.ruby-version +0 -1
- data/.travis.yml +0 -29
- data/CHANGELOG.md +0 -57
- data/CODE_OF_CONDUCT.md +0 -13
- data/Gemfile +0 -4
- data/README.md +0 -120
- data/Rakefile +0 -8
- data/bin/console +0 -14
- data/bin/setup +0 -7
- data/rails_multitenant.gemspec +0 -33
- data/spec/be_multitenant_on_matcher_spec.rb +0 -15
- data/spec/current_spec.rb +0 -118
- data/spec/db/database.yml +0 -3
- data/spec/db/schema.rb +0 -60
- data/spec/external_item_spec.rb +0 -36
- data/spec/external_item_with_optional_org_spec.rb +0 -25
- data/spec/global_context_registry_spec.rb +0 -113
- data/spec/item_spec.rb +0 -78
- data/spec/item_subtype_spec.rb +0 -37
- data/spec/item_with_optional_org_spec.rb +0 -26
- data/spec/middleware_isolated_context_registry_spec.rb +0 -15
- data/spec/rails_multitenant_spec.rb +0 -51
- data/spec/spec_helper.rb +0 -54
data/CODE_OF_CONDUCT.md
DELETED
@@ -1,13 +0,0 @@
|
|
1
|
-
# Contributor Code of Conduct
|
2
|
-
|
3
|
-
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
|
4
|
-
|
5
|
-
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
|
6
|
-
|
7
|
-
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
|
8
|
-
|
9
|
-
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
|
10
|
-
|
11
|
-
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
|
12
|
-
|
13
|
-
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
|
data/Gemfile
DELETED
data/README.md
DELETED
@@ -1,120 +0,0 @@
|
|
1
|
-
# RailsMultitenant
|
2
|
-
|
3
|
-
rails_multitenant is a gem for isolating ActiveRecord models from different tenants. The gem assumes tables storing
|
4
|
-
multi-tenant models include an appropriate tenant id column.
|
5
|
-
|
6
|
-
## Installation
|
7
|
-
|
8
|
-
Add this line to your application's Gemfile:
|
9
|
-
|
10
|
-
```ruby
|
11
|
-
gem 'rails_multitenant'
|
12
|
-
```
|
13
|
-
|
14
|
-
And then execute:
|
15
|
-
|
16
|
-
$ bundle
|
17
|
-
|
18
|
-
Or install it yourself as:
|
19
|
-
|
20
|
-
$ gem install rails_multitenant
|
21
|
-
|
22
|
-
If you're using Rails, there's nothing else you need to do.
|
23
|
-
|
24
|
-
Otherwise, you need to insert `RailsMultitenant::Middleware::IsolatedContextRegistry` into your middleware stack
|
25
|
-
|
26
|
-
## Usage
|
27
|
-
|
28
|
-
The gem supports two multi-tenancy strategies:
|
29
|
-
|
30
|
-
1. Based on a model attribute, typically a foreign key to an entity owned by another service
|
31
|
-
2. Based on a model association
|
32
|
-
|
33
|
-
The gem uses ActiveRecord default scopes to make isolating tenants fairly transparent.
|
34
|
-
|
35
|
-
### Multi-tenancy Based on Model Attributes
|
36
|
-
|
37
|
-
The following model is multi-tenant based on an `organization_id` attribute:
|
38
|
-
|
39
|
-
```ruby
|
40
|
-
class Product < ActiveRecord::Base
|
41
|
-
include RailsMultitenant::MultitenantModel
|
42
|
-
|
43
|
-
multitenant_on :organization_id
|
44
|
-
end
|
45
|
-
```
|
46
|
-
|
47
|
-
The model can then be used as follows:
|
48
|
-
|
49
|
-
```ruby
|
50
|
-
RailsMultitenant::GlobalContextRegistry[:organization_id] = 'my-org'
|
51
|
-
|
52
|
-
# Only returns products from 'my-org'
|
53
|
-
Product.all
|
54
|
-
|
55
|
-
# Returns products across all orgs
|
56
|
-
Product.strip_organization_scope.all
|
57
|
-
|
58
|
-
# Or set the current organization in block form
|
59
|
-
RailsMultitenant::GlobalContextRegistry.with_isolated_registry(organization_id: 'my-org') do
|
60
|
-
# Only returns products from 'my-org'
|
61
|
-
Product.all
|
62
|
-
end
|
63
|
-
```
|
64
|
-
|
65
|
-
By default this adds an ActiveRecord validation to ensure the multi-tenant attribute is present but this can be disabled
|
66
|
-
by passing `required: false` to `multitenant_on`.
|
67
|
-
|
68
|
-
### Multi-tenancy Based on Associated Models
|
69
|
-
|
70
|
-
The following model is multi-tenant based on an `Organization` model:
|
71
|
-
|
72
|
-
```ruby
|
73
|
-
class Product < ActiveRecord::Base
|
74
|
-
include RailsMultitenant::MultitenantModel
|
75
|
-
|
76
|
-
multitenant_on_model :organization
|
77
|
-
end
|
78
|
-
```
|
79
|
-
|
80
|
-
The model can then be used as follows:
|
81
|
-
|
82
|
-
```ruby
|
83
|
-
Organization.current_id = 1
|
84
|
-
|
85
|
-
# Only returns products from organization 1
|
86
|
-
Product.all
|
87
|
-
|
88
|
-
# Use the automatically generated belongs_to association to get
|
89
|
-
# a product's organization
|
90
|
-
Product.first.organization
|
91
|
-
|
92
|
-
# Or set the current organization in block form
|
93
|
-
Organization.as_current_id(1) do
|
94
|
-
# Only returns products from organization 1
|
95
|
-
Product.all
|
96
|
-
end
|
97
|
-
```
|
98
|
-
|
99
|
-
By default this adds an ActiveRecord validation to ensure the tenant model is present but this can be disabled
|
100
|
-
by passing `required: false` to `multitenant_on_model`.
|
101
|
-
|
102
|
-
### Shorthand
|
103
|
-
|
104
|
-
When using `rails-multitenant` in a project, it is common to need to set values in `RailsMultitenant::GlobalContextRegistry` at the rails console.
|
105
|
-
|
106
|
-
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.
|
107
|
-
|
108
|
-
This is mainly intended as a console convenience. Using the long form in source code is fine, and more explicit.
|
109
|
-
|
110
|
-
|
111
|
-
## Development
|
112
|
-
|
113
|
-
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.
|
114
|
-
|
115
|
-
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).
|
116
|
-
|
117
|
-
## Contributing
|
118
|
-
|
119
|
-
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.
|
120
|
-
|
data/Rakefile
DELETED
data/bin/console
DELETED
@@ -1,14 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require "bundler/setup"
|
4
|
-
require "rails_multitenant"
|
5
|
-
|
6
|
-
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
-
# with your gem easier. You can also use a different console, if you like.
|
8
|
-
|
9
|
-
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
-
# require "pry"
|
11
|
-
# Pry.start
|
12
|
-
|
13
|
-
require "irb"
|
14
|
-
IRB.start
|
data/bin/setup
DELETED
data/rails_multitenant.gemspec
DELETED
@@ -1,33 +0,0 @@
|
|
1
|
-
# coding: utf-8
|
2
|
-
lib = File.expand_path('../lib', __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require 'rails_multitenant/version'
|
5
|
-
|
6
|
-
Gem::Specification.new do |spec|
|
7
|
-
spec.name = "rails_multitenant"
|
8
|
-
spec.version = RailsMultitenant::VERSION
|
9
|
-
spec.authors = ["Pat Breault"]
|
10
|
-
spec.email = ["pbreault@salsify.com"]
|
11
|
-
spec.summary = %q{Automatically configures multiple tenants in a Rails environment}
|
12
|
-
spec.description = %q{Handles multiple tenants in a Rails environment}
|
13
|
-
spec.homepage = "https://github.com/salsify/rails-multitenant"
|
14
|
-
spec.license = 'MIT'
|
15
|
-
|
16
|
-
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
17
|
-
spec.test_files = Dir.glob('spec/**/*')
|
18
|
-
spec.require_paths = ["lib"]
|
19
|
-
|
20
|
-
spec.required_ruby_version = '>= 2.1.0'
|
21
|
-
|
22
|
-
spec.add_development_dependency "bundler", "~> 1.10"
|
23
|
-
|
24
|
-
spec.add_dependency 'activerecord', ENV.fetch('RAILS_VERSION', ['>= 4.1', '< 5.3'])
|
25
|
-
spec.add_dependency 'activesupport', ENV.fetch('RAILS_VERSION', ['>= 4.1', '< 5.3'])
|
26
|
-
|
27
|
-
spec.add_development_dependency 'coveralls'
|
28
|
-
spec.add_development_dependency 'database_cleaner', '>= 1.2'
|
29
|
-
spec.add_development_dependency 'rake', '< 11.0'
|
30
|
-
spec.add_development_dependency 'rspec', '~> 2'
|
31
|
-
spec.add_development_dependency 'simplecov', '~> 0.15.1'
|
32
|
-
spec.add_development_dependency 'sqlite3', '~> 1.3.0'
|
33
|
-
end
|
@@ -1,15 +0,0 @@
|
|
1
|
-
require 'rails_multitenant/rspec'
|
2
|
-
|
3
|
-
describe "be_multitenant_on matcher" do
|
4
|
-
it "accepts a valid context field id" do
|
5
|
-
expect(ExternalItem).to be_multitenant_on(:external_organization_id)
|
6
|
-
end
|
7
|
-
|
8
|
-
it "rejects an invalid context field id" do
|
9
|
-
expect(ExternalItem).not_to be_multitenant_on(:other_field)
|
10
|
-
end
|
11
|
-
|
12
|
-
it "rejects classes that don't have a context field id" do
|
13
|
-
expect(String).not_to be_multitenant_on(:other_field)
|
14
|
-
end
|
15
|
-
end
|
data/spec/current_spec.rb
DELETED
@@ -1,118 +0,0 @@
|
|
1
|
-
describe RailsMultitenant::GlobalContextRegistry::Current do
|
2
|
-
|
3
|
-
class TestClass
|
4
|
-
include RailsMultitenant::GlobalContextRegistry::Current
|
5
|
-
provide_default :new
|
6
|
-
|
7
|
-
attr_accessor :id
|
8
|
-
|
9
|
-
def initialize(id: :default)
|
10
|
-
@id = id
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
class SubClass < TestClass
|
15
|
-
end
|
16
|
-
|
17
|
-
class DependentClass
|
18
|
-
include RailsMultitenant::GlobalContextRegistry::Current
|
19
|
-
provide_default { new }
|
20
|
-
global_context_dependent_on TestClass
|
21
|
-
end
|
22
|
-
|
23
|
-
class NoDefaultTestClass
|
24
|
-
include RailsMultitenant::GlobalContextRegistry::Current
|
25
|
-
end
|
26
|
-
|
27
|
-
describe 'current' do
|
28
|
-
it 'returns default value when supplied' do
|
29
|
-
expect(TestClass.current.id).to eq(:default)
|
30
|
-
expect(SubClass.current.id).to eq(:default)
|
31
|
-
end
|
32
|
-
|
33
|
-
it 'returns nil when no default supplied' do
|
34
|
-
expect(NoDefaultTestClass.current).to be_nil
|
35
|
-
end
|
36
|
-
end
|
37
|
-
|
38
|
-
describe 'current!' do
|
39
|
-
it 'returns current value when set' do
|
40
|
-
expect(TestClass.current.id).to eq(:default)
|
41
|
-
end
|
42
|
-
|
43
|
-
it 'raises an error when current not set' do
|
44
|
-
NoDefaultTestClass.clear_current!
|
45
|
-
expect { NoDefaultTestClass.current! }.to raise_error
|
46
|
-
end
|
47
|
-
end
|
48
|
-
|
49
|
-
describe 'current=' do
|
50
|
-
it 'stores the provided object' do
|
51
|
-
provided = TestClass.new(id: :provided)
|
52
|
-
TestClass.current = provided
|
53
|
-
expect(TestClass.current).to equal(provided)
|
54
|
-
end
|
55
|
-
|
56
|
-
it 'clears dependencies' do
|
57
|
-
dependent = DependentClass.current
|
58
|
-
TestClass.current = TestClass.new
|
59
|
-
expect(DependentClass.current).not_to equal(dependent)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
describe 'current?' do
|
64
|
-
it 'returns false when uninitialized' do
|
65
|
-
TestClass.clear_current!
|
66
|
-
expect(TestClass.current?).to be false
|
67
|
-
end
|
68
|
-
|
69
|
-
it 'returns true when initialized' do
|
70
|
-
TestClass.current = TestClass.new
|
71
|
-
expect(TestClass.current?).to be true
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
|
-
describe 'as_current' do
|
76
|
-
let(:test_class1) { TestClass.new }
|
77
|
-
let(:test_class2) { TestClass.new }
|
78
|
-
|
79
|
-
it 'sets and restores current' do
|
80
|
-
TestClass.current = test_class1
|
81
|
-
TestClass.as_current(test_class2) do
|
82
|
-
expect(TestClass.current).to equal(test_class2)
|
83
|
-
end
|
84
|
-
expect(TestClass.current).to equal(test_class1)
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
context 'instance methods' do
|
89
|
-
describe 'current?' do
|
90
|
-
it 'returns false when not the current instance' do
|
91
|
-
TestClass.clear_current!
|
92
|
-
expect(TestClass.new.current?).to be false
|
93
|
-
|
94
|
-
TestClass.current = TestClass.new
|
95
|
-
expect(TestClass.new.current?).to be false
|
96
|
-
end
|
97
|
-
|
98
|
-
it 'returns true when it is the current instance' do
|
99
|
-
test_class = TestClass.new
|
100
|
-
TestClass.current = test_class
|
101
|
-
expect(test_class.current?).to be true
|
102
|
-
end
|
103
|
-
end
|
104
|
-
|
105
|
-
describe 'as_current' do
|
106
|
-
let(:test_class1) { TestClass.new }
|
107
|
-
let(:test_class2) { TestClass.new }
|
108
|
-
|
109
|
-
it 'sets and restores current' do
|
110
|
-
TestClass.current = test_class1
|
111
|
-
test_class2.as_current do
|
112
|
-
expect(TestClass.current).to equal(test_class2)
|
113
|
-
end
|
114
|
-
expect(TestClass.current).to equal(test_class1)
|
115
|
-
end
|
116
|
-
end
|
117
|
-
end
|
118
|
-
end
|
data/spec/db/database.yml
DELETED
data/spec/db/schema.rb
DELETED
@@ -1,60 +0,0 @@
|
|
1
|
-
# encoding: UTF-8
|
2
|
-
|
3
|
-
ActiveRecord::Schema.define(version: 0) do
|
4
|
-
|
5
|
-
create_table(:organizations, force: true)
|
6
|
-
|
7
|
-
create_table(:dependent_models, force: true)
|
8
|
-
|
9
|
-
create_table(:items, force: true) do |t|
|
10
|
-
t.integer :organization_id
|
11
|
-
t.string :type
|
12
|
-
end
|
13
|
-
|
14
|
-
create_table(:item_with_optional_orgs, force: true) do |t|
|
15
|
-
t.integer :organization_id
|
16
|
-
t.string :type
|
17
|
-
end
|
18
|
-
|
19
|
-
create_table(:external_items, force: true) do |t|
|
20
|
-
t.integer :external_organization_id
|
21
|
-
end
|
22
|
-
|
23
|
-
create_table(:external_item_with_optional_orgs, force: true) do |t|
|
24
|
-
t.integer :external_organization_id
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
class Organization < ActiveRecord::Base
|
29
|
-
include RailsMultitenant::GlobalContextRegistry::CurrentInstance
|
30
|
-
end
|
31
|
-
|
32
|
-
class DependentModel < ActiveRecord::Base
|
33
|
-
include RailsMultitenant::GlobalContextRegistry::CurrentInstance
|
34
|
-
global_context_dependent_on Organization
|
35
|
-
end
|
36
|
-
|
37
|
-
class Item < ActiveRecord::Base
|
38
|
-
include RailsMultitenant::MultitenantModel
|
39
|
-
multitenant_on_model :organization
|
40
|
-
end
|
41
|
-
|
42
|
-
class ItemWithOptionalOrg < ActiveRecord::Base
|
43
|
-
include RailsMultitenant::MultitenantModel
|
44
|
-
multitenant_on_model :organization, required: false
|
45
|
-
end
|
46
|
-
|
47
|
-
class ItemSubtype < Item
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
class ExternalItem < ActiveRecord::Base
|
52
|
-
include RailsMultitenant::MultitenantModel
|
53
|
-
multitenant_on :external_organization_id
|
54
|
-
end
|
55
|
-
|
56
|
-
class ExternalItemWithOptionalOrg < ActiveRecord::Base
|
57
|
-
include RailsMultitenant::MultitenantModel
|
58
|
-
multitenant_on :external_organization_id, required: false
|
59
|
-
end
|
60
|
-
|
data/spec/external_item_spec.rb
DELETED
@@ -1,36 +0,0 @@
|
|
1
|
-
include RailsMultitenant
|
2
|
-
|
3
|
-
describe ExternalItem do
|
4
|
-
|
5
|
-
let!(:external_item1) { as_external_org(1) { ExternalItem.create! } }
|
6
|
-
|
7
|
-
let!(:external_item2) { as_external_org(2) { ExternalItem.create! } }
|
8
|
-
let!(:external_item3) { as_external_org(2) { ExternalItem.create! } }
|
9
|
-
|
10
|
-
specify 'org1 has the correct external items' do
|
11
|
-
as_external_org(1) do
|
12
|
-
expect(ExternalItem.all).to eq [external_item1]
|
13
|
-
end
|
14
|
-
end
|
15
|
-
|
16
|
-
specify 'org2 has the correct external items' do
|
17
|
-
as_external_org(2) do
|
18
|
-
expect(ExternalItem.all).to match_array [external_item2, external_item3]
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
it 'does not return external items from other orgs' do
|
23
|
-
as_external_org(2) do
|
24
|
-
expect(ExternalItem.where(id: external_item1.id)).to eq []
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
it 'allows the organization scope to be removed' do
|
29
|
-
expect(ExternalItem.strip_external_organization_scope.count).to eq 3
|
30
|
-
end
|
31
|
-
|
32
|
-
def as_external_org(id, &block)
|
33
|
-
GlobalContextRegistry.with_isolated_registry(external_organization_id: id, &block)
|
34
|
-
end
|
35
|
-
|
36
|
-
end
|