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.
Files changed (39) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE.txt +21 -0
  3. data/lib/rails_multitenant.rb +7 -4
  4. data/lib/rails_multitenant/global_context_registry.rb +43 -198
  5. data/lib/rails_multitenant/global_context_registry/current.rb +98 -0
  6. data/lib/rails_multitenant/global_context_registry/current_instance.rb +103 -0
  7. data/lib/rails_multitenant/global_context_registry/registry_dependent_on.rb +13 -0
  8. data/lib/rails_multitenant/middleware/extensions.rb +3 -3
  9. data/lib/rails_multitenant/middleware/isolated_context_registry.rb +2 -0
  10. data/lib/rails_multitenant/middleware/railtie.rb +3 -1
  11. data/lib/rails_multitenant/multitenant_model.rb +6 -2
  12. data/lib/rails_multitenant/rspec.rb +2 -0
  13. data/lib/rails_multitenant/version.rb +3 -1
  14. metadata +69 -76
  15. data/.gitignore +0 -14
  16. data/.rspec +0 -3
  17. data/.ruby-version +0 -1
  18. data/.travis.yml +0 -29
  19. data/CHANGELOG.md +0 -57
  20. data/CODE_OF_CONDUCT.md +0 -13
  21. data/Gemfile +0 -4
  22. data/README.md +0 -120
  23. data/Rakefile +0 -8
  24. data/bin/console +0 -14
  25. data/bin/setup +0 -7
  26. data/rails_multitenant.gemspec +0 -33
  27. data/spec/be_multitenant_on_matcher_spec.rb +0 -15
  28. data/spec/current_spec.rb +0 -118
  29. data/spec/db/database.yml +0 -3
  30. data/spec/db/schema.rb +0 -60
  31. data/spec/external_item_spec.rb +0 -36
  32. data/spec/external_item_with_optional_org_spec.rb +0 -25
  33. data/spec/global_context_registry_spec.rb +0 -113
  34. data/spec/item_spec.rb +0 -78
  35. data/spec/item_subtype_spec.rb +0 -37
  36. data/spec/item_with_optional_org_spec.rb +0 -26
  37. data/spec/middleware_isolated_context_registry_spec.rb +0 -15
  38. data/spec/rails_multitenant_spec.rb +0 -51
  39. data/spec/spec_helper.rb +0 -54
@@ -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
@@ -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,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
@@ -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
@@ -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