rails_multitenant 0.12.0 → 0.13.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE.txt +21 -0
  3. data/lib/rails_multitenant.rb +7 -4
  4. data/lib/rails_multitenant/global_context_registry.rb +22 -5
  5. data/lib/rails_multitenant/middleware/extensions.rb +3 -3
  6. data/lib/rails_multitenant/middleware/isolated_context_registry.rb +2 -0
  7. data/lib/rails_multitenant/middleware/railtie.rb +3 -1
  8. data/lib/rails_multitenant/multitenant_model.rb +3 -1
  9. data/lib/rails_multitenant/rspec.rb +2 -0
  10. data/lib/rails_multitenant/version.rb +3 -1
  11. metadata +20 -49
  12. data/.gitignore +0 -16
  13. data/.rspec +0 -3
  14. data/.ruby-version +0 -1
  15. data/.travis.yml +0 -16
  16. data/Appraisals +0 -28
  17. data/CHANGELOG.md +0 -61
  18. data/CODE_OF_CONDUCT.md +0 -13
  19. data/Gemfile +0 -4
  20. data/README.md +0 -120
  21. data/Rakefile +0 -8
  22. data/bin/console +0 -14
  23. data/bin/setup +0 -7
  24. data/gemfiles/rails_4.2.gemfile +0 -9
  25. data/gemfiles/rails_5.0.gemfile +0 -9
  26. data/gemfiles/rails_5.1.gemfile +0 -8
  27. data/gemfiles/rails_5.2.gemfile +0 -8
  28. data/gemfiles/rails_6.0.gemfile +0 -8
  29. data/rails_multitenant.gemspec +0 -32
  30. data/spec/be_multitenant_on_matcher_spec.rb +0 -15
  31. data/spec/current_spec.rb +0 -118
  32. data/spec/db/database.yml +0 -3
  33. data/spec/db/schema.rb +0 -60
  34. data/spec/external_item_spec.rb +0 -36
  35. data/spec/external_item_with_optional_org_spec.rb +0 -25
  36. data/spec/global_context_registry_spec.rb +0 -113
  37. data/spec/item_spec.rb +0 -78
  38. data/spec/item_subtype_spec.rb +0 -37
  39. data/spec/item_with_optional_org_spec.rb +0 -26
  40. data/spec/middleware_isolated_context_registry_spec.rb +0 -15
  41. data/spec/rails_multitenant_spec.rb +0 -51
  42. data/spec/spec_helper.rb +0 -54
data/Gemfile DELETED
@@ -1,4 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in rails_multitenant.gemspec
4
- gemspec
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
@@ -1,8 +0,0 @@
1
- require 'bundler/gem_tasks'
2
-
3
- require 'rspec/core/rake_task'
4
- RSpec::Core::RakeTask.new(:spec) do |task|
5
- task.verbose = false
6
- end
7
-
8
- task default: :spec
@@ -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
@@ -1,7 +0,0 @@
1
- #!/bin/bash
2
- set -euo pipefail
3
- IFS=$'\n\t'
4
-
5
- bundle install
6
-
7
- # Do any other automated setup that you need to do here
@@ -1,9 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "sqlite3", "~> 1.3.6"
6
- gem "activerecord", "4.2.11.1"
7
- gem "activesupport", "4.2.11.1"
8
-
9
- gemspec path: "../"
@@ -1,9 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "sqlite3", "~> 1.3.6"
6
- gem "activerecord", "5.0.7.2"
7
- gem "activesupport", "5.0.7.2"
8
-
9
- gemspec path: "../"
@@ -1,8 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "5.1.7"
6
- gem "activesupport", "5.1.7"
7
-
8
- gemspec path: "../"
@@ -1,8 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "5.2.3"
6
- gem "activesupport", "5.2.3"
7
-
8
- gemspec path: "../"
@@ -1,8 +0,0 @@
1
- # This file was generated by Appraisal
2
-
3
- source "https://rubygems.org"
4
-
5
- gem "activerecord", "6.0.0.rc2"
6
- gem "activesupport", "6.0.0.rc2"
7
-
8
- gemspec path: "../"
@@ -1,32 +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.4.0'
21
-
22
- spec.add_dependency 'activerecord', '>= 4.2', '< 6.1'
23
- spec.add_dependency 'activesupport', '>= 4.2', '< 6.1'
24
-
25
- spec.add_development_dependency 'appraisal'
26
- spec.add_development_dependency 'coveralls'
27
- spec.add_development_dependency 'database_cleaner', '>= 1.2'
28
- spec.add_development_dependency 'rake', '>= 12.0'
29
- spec.add_development_dependency 'rspec', '~> 3.8.0'
30
- spec.add_development_dependency 'simplecov', '~> 0.15.1'
31
- spec.add_development_dependency 'sqlite3', '~> 1.4.0'
32
- 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
@@ -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('No current NoDefaultTestClass set')
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
@@ -1,3 +0,0 @@
1
- sqlite3:
2
- adapter: sqlite3
3
- database: ":memory:"
@@ -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
-
@@ -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