activerecord-multi-tenant 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/active-record-multi-tenant-tests.yml +80 -0
- data/.gitignore +6 -0
- data/.readthedocs.yaml +15 -0
- data/.rspec +0 -0
- data/.rubocop.yml +51 -0
- data/Appraisals +0 -0
- data/CHANGELOG.md +6 -0
- data/Gemfile +3 -1
- data/LICENSE +0 -0
- data/README.md +2 -1
- data/Rakefile +1 -1
- data/activerecord-multi-tenant.gemspec +28 -22
- data/docker-compose.yml +24 -18
- data/docs/.gitignore +3 -0
- data/docs/Makefile +28 -0
- data/docs/api-reference.sh +10 -0
- data/docs/requirements.in +4 -0
- data/docs/requirements.txt +62 -0
- data/docs/source/_static/api-reference/ActiveRecord/Associations/Association.html +285 -0
- data/docs/source/_static/api-reference/ActiveRecord/Associations/ClassMethods.html +255 -0
- data/docs/source/_static/api-reference/ActiveRecord/Associations.html +117 -0
- data/docs/source/_static/api-reference/ActiveRecord/ConnectionAdapters/SchemaStatements.html +232 -0
- data/docs/source/_static/api-reference/ActiveRecord/ConnectionAdapters.html +126 -0
- data/docs/source/_static/api-reference/ActiveRecord/QueryMethods.html +336 -0
- data/docs/source/_static/api-reference/ActiveRecord/SchemaDumper.html +121 -0
- data/docs/source/_static/api-reference/ActiveRecord.html +130 -0
- data/docs/source/_static/api-reference/MultiTenant/ArelTenantVisitor.html +755 -0
- data/docs/source/_static/api-reference/MultiTenant/ArelVisitorsDepthFirst.html +208 -0
- data/docs/source/_static/api-reference/MultiTenant/BaseTenantEnforcementClause.html +462 -0
- data/docs/source/_static/api-reference/MultiTenant/Context.html +659 -0
- data/docs/source/_static/api-reference/MultiTenant/ControllerExtensions.html +202 -0
- data/docs/source/_static/api-reference/MultiTenant/CopyFromClient.html +186 -0
- data/docs/source/_static/api-reference/MultiTenant/CopyFromClientHelper.html +362 -0
- data/docs/source/_static/api-reference/MultiTenant/Current.html +124 -0
- data/docs/source/_static/api-reference/MultiTenant/DatabaseStatements.html +366 -0
- data/docs/source/_static/api-reference/MultiTenant/FastTruncate.html +226 -0
- data/docs/source/_static/api-reference/MultiTenant/MigrationExtensions.html +554 -0
- data/docs/source/_static/api-reference/MultiTenant/MissingTenantError.html +124 -0
- data/docs/source/_static/api-reference/MultiTenant/ModelExtensionsClassMethods.html +492 -0
- data/docs/source/_static/api-reference/MultiTenant/QueryMonitor.html +257 -0
- data/docs/source/_static/api-reference/MultiTenant/Table.html +419 -0
- data/docs/source/_static/api-reference/MultiTenant/TenantEnforcementClause.html +148 -0
- data/docs/source/_static/api-reference/MultiTenant/TenantIsImmutable.html +135 -0
- data/docs/source/_static/api-reference/MultiTenant/TenantJoinEnforcementClause.html +310 -0
- data/docs/source/_static/api-reference/MultiTenant/TenantValueVisitor.html +239 -0
- data/docs/source/_static/api-reference/MultiTenant.html +1454 -0
- data/docs/source/_static/api-reference/MultiTenantFindBy.html +180 -0
- data/docs/source/_static/api-reference/Sidekiq/Client.html +302 -0
- data/docs/source/_static/api-reference/Sidekiq/Middleware/MultiTenant/Client.html +217 -0
- data/docs/source/_static/api-reference/Sidekiq/Middleware/MultiTenant/Server.html +219 -0
- data/docs/source/_static/api-reference/Sidekiq/Middleware/MultiTenant.html +126 -0
- data/docs/source/_static/api-reference/Sidekiq.html +126 -0
- data/docs/source/_static/api-reference/_index.html +399 -0
- data/docs/source/_static/api-reference/class_list.html +51 -0
- data/docs/source/_static/api-reference/css/common.css +1 -0
- data/docs/source/_static/api-reference/css/full_list.css +58 -0
- data/docs/source/_static/api-reference/css/style.css +497 -0
- data/docs/source/_static/api-reference/file.README.html +167 -0
- data/docs/source/_static/api-reference/file_list.html +56 -0
- data/docs/source/_static/api-reference/frames.html +17 -0
- data/docs/source/_static/api-reference/index.html +167 -0
- data/docs/source/_static/api-reference/js/app.js +314 -0
- data/docs/source/_static/api-reference/js/full_list.js +216 -0
- data/docs/source/_static/api-reference/js/jquery.js +4 -0
- data/docs/source/_static/api-reference/method_list.html +715 -0
- data/docs/source/_static/api-reference/top-level-namespace.html +126 -0
- data/docs/source/_templates/.gitignore +4 -0
- data/docs/source/api-reference.rst +8 -0
- data/docs/source/appendix.rst +26 -0
- data/docs/source/changelog.rst +8 -0
- data/docs/source/community-and-support.rst +26 -0
- data/docs/source/conf.py +30 -0
- data/docs/source/contributing.rst +70 -0
- data/docs/source/getting-started.rst +37 -0
- data/docs/source/guides-and-tutorials.rst +129 -0
- data/docs/source/index.rst +54 -0
- data/docs/source/introduction.rst +33 -0
- data/docs/source/license.rst +22 -0
- data/docs/source/troubleshooting.rst +41 -0
- data/docs/source/usage-guide.rst +59 -0
- data/lib/activerecord-multi-tenant/arel_visitors_depth_first.rb +183 -174
- data/lib/activerecord-multi-tenant/controller_extensions.rb +15 -4
- data/lib/activerecord-multi-tenant/copy_from_client.rb +4 -0
- data/lib/activerecord-multi-tenant/fast_truncate.rb +4 -2
- data/lib/activerecord-multi-tenant/habtm.rb +50 -0
- data/lib/activerecord-multi-tenant/migrations.rb +18 -8
- data/lib/activerecord-multi-tenant/model_extensions.rb +78 -37
- data/lib/activerecord-multi-tenant/multi_tenant.rb +40 -21
- data/lib/activerecord-multi-tenant/query_monitor.rb +21 -5
- data/lib/activerecord-multi-tenant/query_rewriter.rb +111 -80
- data/lib/activerecord-multi-tenant/sidekiq.rb +31 -20
- data/lib/activerecord-multi-tenant/version.rb +1 -1
- data/lib/activerecord-multi-tenant.rb +3 -12
- data/lib/activerecord_multi_tenant.rb +12 -0
- data/spec/activerecord-multi-tenant/associations_spec.rb +21 -0
- data/spec/activerecord-multi-tenant/controller_extensions_spec.rb +3 -2
- data/spec/activerecord-multi-tenant/fast_truncate_spec.rb +8 -6
- data/spec/activerecord-multi-tenant/model_extensions_spec.rb +233 -153
- data/spec/activerecord-multi-tenant/multi_tenant_spec.rb +15 -13
- data/spec/activerecord-multi-tenant/query_rewriter_spec.rb +60 -59
- data/spec/activerecord-multi-tenant/record_callback_spec.rb +0 -0
- data/spec/activerecord-multi-tenant/record_finding_spec.rb +11 -11
- data/spec/activerecord-multi-tenant/record_modifications_spec.rb +4 -4
- data/spec/activerecord-multi-tenant/sidekiq_spec.rb +10 -10
- data/spec/database.yml +0 -0
- data/spec/schema.rb +20 -2
- data/spec/spec_helper.rb +46 -17
- data/spec/support/format_sql.rb +20 -0
- metadata +130 -25
- data/.github/workflows/CI.yml +0 -47
- data/gemfiles/.bundle/config +0 -2
- data/gemfiles/active_record_6.0.gemfile +0 -8
- data/gemfiles/active_record_6.1.gemfile +0 -8
- data/gemfiles/active_record_7.0.gemfile +0 -8
- data/gemfiles/rails_6.0.gemfile +0 -8
- data/gemfiles/rails_6.1.gemfile +0 -8
- data/gemfiles/rails_7.0.gemfile +0 -8
- data/lib/activerecord-multi-tenant/with_lock.rb +0 -15
- data/spec/activerecord-multi-tenant/schema_dumper_tester.rb +0 -0
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
RSpec.describe MultiTenant do
|
|
4
|
-
describe
|
|
6
|
+
describe '.load_current_tenant!' do
|
|
5
7
|
let(:fake_tenant) { OpenStruct.new(id: 1) }
|
|
6
8
|
let(:mock_klass) { double(find: fake_tenant) }
|
|
7
9
|
|
|
@@ -14,14 +16,14 @@ RSpec.describe MultiTenant do
|
|
|
14
16
|
MultiTenant.default_tenant_class = @original_default_class
|
|
15
17
|
end
|
|
16
18
|
|
|
17
|
-
it
|
|
19
|
+
it 'sets and returns the loaded current_tenant' do
|
|
18
20
|
expect(mock_klass).to receive(:find).once.with(1)
|
|
19
21
|
MultiTenant.current_tenant = 1
|
|
20
22
|
expect(MultiTenant.load_current_tenant!).to eq(fake_tenant)
|
|
21
23
|
expect(MultiTenant.current_tenant).to eq(fake_tenant)
|
|
22
24
|
end
|
|
23
25
|
|
|
24
|
-
it
|
|
26
|
+
it 'respects `.with` lifecycle' do
|
|
25
27
|
expect(mock_klass).to receive(:find).once.with(2)
|
|
26
28
|
expect(MultiTenant.current_tenant).to eq(nil)
|
|
27
29
|
MultiTenant.with(2) do
|
|
@@ -31,34 +33,34 @@ RSpec.describe MultiTenant do
|
|
|
31
33
|
expect(MultiTenant.current_tenant).to eq(nil)
|
|
32
34
|
end
|
|
33
35
|
|
|
34
|
-
context
|
|
35
|
-
it
|
|
36
|
+
context 'with a loaded current_tenant' do
|
|
37
|
+
it 'returns the tenant without fetching it' do
|
|
36
38
|
expect(mock_klass).not_to receive(:find)
|
|
37
39
|
MultiTenant.current_tenant = fake_tenant
|
|
38
40
|
expect(MultiTenant.load_current_tenant!).to eq(fake_tenant)
|
|
39
41
|
end
|
|
40
42
|
end
|
|
41
43
|
|
|
42
|
-
context
|
|
43
|
-
it
|
|
44
|
+
context 'with a nil current_tenant' do
|
|
45
|
+
it 'raises an error, as there is not enough information to load the tenant' do
|
|
44
46
|
expect(mock_klass).not_to receive(:find)
|
|
45
|
-
expect
|
|
47
|
+
expect do
|
|
46
48
|
MultiTenant.load_current_tenant!
|
|
47
|
-
|
|
49
|
+
end.to raise_error(RuntimeError, 'MultiTenant.current_tenant must be set to load')
|
|
48
50
|
end
|
|
49
51
|
end
|
|
50
52
|
|
|
51
|
-
context
|
|
53
|
+
context 'without a default class set' do
|
|
52
54
|
before do
|
|
53
55
|
MultiTenant.default_tenant_class = nil
|
|
54
56
|
end
|
|
55
57
|
|
|
56
|
-
it
|
|
58
|
+
it 'raises an error, as there is not enough information to load the tenant' do
|
|
57
59
|
expect(mock_klass).not_to receive(:find)
|
|
58
60
|
MultiTenant.current_tenant = 1
|
|
59
|
-
expect
|
|
61
|
+
expect do
|
|
60
62
|
MultiTenant.load_current_tenant!
|
|
61
|
-
|
|
63
|
+
end.to raise_error(RuntimeError, 'Only have tenant id, and no default tenant class set')
|
|
62
64
|
end
|
|
63
65
|
end
|
|
64
66
|
end
|
|
@@ -1,112 +1,113 @@
|
|
|
1
|
-
|
|
1
|
+
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
require 'spec_helper'
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
let!(:
|
|
8
|
-
let!(:
|
|
5
|
+
describe 'Query Rewriter' do
|
|
6
|
+
context 'when bulk updating' do
|
|
7
|
+
let!(:account) { Account.create!(name: 'Test Account') }
|
|
8
|
+
let!(:project) { Project.create(name: 'Project 1', account: account) }
|
|
9
|
+
let!(:manager) { Manager.create(name: 'Manager', project: project, account: account) }
|
|
9
10
|
|
|
10
|
-
it
|
|
11
|
-
expect
|
|
11
|
+
it 'updates the records' do
|
|
12
|
+
expect do
|
|
12
13
|
MultiTenant.with(account) do
|
|
13
|
-
Project.joins(:manager).update_all(name:
|
|
14
|
+
Project.joins(:manager).update_all(name: 'New Name')
|
|
14
15
|
end
|
|
15
|
-
|
|
16
|
+
end.to change { project.reload.name }.from('Project 1').to('New Name')
|
|
16
17
|
end
|
|
17
18
|
|
|
18
|
-
it
|
|
19
|
-
expect
|
|
20
|
-
Project.joins(:manager).update_all(name:
|
|
21
|
-
|
|
19
|
+
it 'updates the records without a current tenant' do
|
|
20
|
+
expect do
|
|
21
|
+
Project.joins(:manager).update_all(name: 'New Name')
|
|
22
|
+
end.to change { project.reload.name }.from('Project 1').to('New Name')
|
|
22
23
|
end
|
|
23
24
|
|
|
24
|
-
it
|
|
25
|
-
expect
|
|
25
|
+
it 'update the record' do
|
|
26
|
+
expect do
|
|
26
27
|
MultiTenant.with(account) do
|
|
27
|
-
project.update(name:
|
|
28
|
+
project.update(name: 'New Name')
|
|
28
29
|
end
|
|
29
|
-
|
|
30
|
+
end.to change { project.reload.name }.from('Project 1').to('New Name')
|
|
30
31
|
end
|
|
31
32
|
|
|
32
|
-
it
|
|
33
|
-
expect
|
|
34
|
-
project.update(name:
|
|
35
|
-
|
|
33
|
+
it 'update the record without a current tenant' do
|
|
34
|
+
expect do
|
|
35
|
+
project.update(name: 'New Name')
|
|
36
|
+
end.to change { project.reload.name }.from('Project 1').to('New Name')
|
|
36
37
|
end
|
|
37
38
|
end
|
|
38
39
|
|
|
39
|
-
context
|
|
40
|
-
let!(:account) { Account.create!(name:
|
|
41
|
-
let!(:project1) { Project.create(name:
|
|
42
|
-
let!(:project2) { Project.create(name:
|
|
43
|
-
let!(:project3) { Project.create(name:
|
|
44
|
-
let!(:manager1) { Manager.create(name:
|
|
45
|
-
let!(:manager2) { Manager.create(name:
|
|
40
|
+
context 'when bulk deleting' do
|
|
41
|
+
let!(:account) { Account.create!(name: 'Test Account') }
|
|
42
|
+
let!(:project1) { Project.create(name: 'Project 1', account: account) }
|
|
43
|
+
let!(:project2) { Project.create(name: 'Project 2', account: account) }
|
|
44
|
+
let!(:project3) { Project.create(name: 'Project 3', account: account) }
|
|
45
|
+
let!(:manager1) { Manager.create(name: 'Manager 1', project: project1, account: account) }
|
|
46
|
+
let!(:manager2) { Manager.create(name: 'Manager 2', project: project2, account: account) }
|
|
46
47
|
|
|
47
|
-
it
|
|
48
|
-
expect
|
|
48
|
+
it 'delete_all the records' do
|
|
49
|
+
expect do
|
|
49
50
|
MultiTenant.with(account) do
|
|
50
51
|
Project.joins(:manager).delete_all
|
|
51
52
|
end
|
|
52
|
-
|
|
53
|
+
end.to change { Project.count }.from(3).to(1)
|
|
53
54
|
end
|
|
54
55
|
|
|
55
|
-
it
|
|
56
|
-
expect
|
|
56
|
+
it 'delete_all the records without a current tenant' do
|
|
57
|
+
expect do
|
|
57
58
|
Project.joins(:manager).delete_all
|
|
58
|
-
|
|
59
|
+
end.to change { Project.count }.from(3).to(1)
|
|
59
60
|
end
|
|
60
61
|
|
|
61
|
-
it
|
|
62
|
-
expect
|
|
62
|
+
it 'delete the record' do
|
|
63
|
+
expect do
|
|
63
64
|
MultiTenant.with(account) do
|
|
64
65
|
project1.delete
|
|
65
66
|
Project.delete(project2.id)
|
|
66
67
|
end
|
|
67
|
-
|
|
68
|
+
end.to change { Project.count }.from(3).to(1)
|
|
68
69
|
end
|
|
69
70
|
|
|
70
|
-
it
|
|
71
|
-
expect
|
|
71
|
+
it 'delete the record without a current tenant' do
|
|
72
|
+
expect do
|
|
72
73
|
project1.delete
|
|
73
74
|
Project.delete(project2.id)
|
|
74
|
-
|
|
75
|
+
end.to change { Project.count }.from(3).to(1)
|
|
75
76
|
end
|
|
76
77
|
|
|
77
|
-
it
|
|
78
|
-
expect
|
|
78
|
+
it 'destroy the record' do
|
|
79
|
+
expect do
|
|
79
80
|
MultiTenant.with(account) do
|
|
80
81
|
project1.destroy
|
|
81
82
|
Project.destroy(project2.id)
|
|
82
83
|
end
|
|
83
|
-
|
|
84
|
+
end.to change { Project.count }.from(3).to(1)
|
|
84
85
|
end
|
|
85
86
|
|
|
86
|
-
it
|
|
87
|
-
expect
|
|
87
|
+
it 'destroy the record without a current tenant' do
|
|
88
|
+
expect do
|
|
88
89
|
project1.destroy
|
|
89
90
|
Project.destroy(project2.id)
|
|
90
|
-
|
|
91
|
+
end.to change { Project.count }.from(3).to(1)
|
|
91
92
|
end
|
|
92
93
|
end
|
|
93
94
|
|
|
94
|
-
context
|
|
95
|
-
it
|
|
96
|
-
expect
|
|
97
|
-
ActiveRecord::Base.connection.update(
|
|
98
|
-
|
|
95
|
+
context 'when update without arel' do
|
|
96
|
+
it 'can call method' do
|
|
97
|
+
expect do
|
|
98
|
+
ActiveRecord::Base.connection.update('SELECT 1')
|
|
99
|
+
end.not_to raise_error
|
|
99
100
|
end
|
|
100
101
|
end
|
|
101
102
|
|
|
102
|
-
context
|
|
103
|
-
let!(:account) { Account.create!(name:
|
|
103
|
+
context 'when joining with a model with a default scope' do
|
|
104
|
+
let!(:account) { Account.create!(name: 'Test Account') }
|
|
104
105
|
|
|
105
|
-
it
|
|
106
|
-
alive = Domain.create(name:
|
|
107
|
-
deleted = Domain.create(name:
|
|
108
|
-
page_in_alive_domain = Page.create(name:
|
|
109
|
-
|
|
106
|
+
it 'fetches only records within the default scope' do
|
|
107
|
+
alive = Domain.create(name: 'alive', account: account)
|
|
108
|
+
deleted = Domain.create(name: 'deleted', deleted: true, account: account)
|
|
109
|
+
page_in_alive_domain = Page.create(name: 'alive', account: account, domain: alive)
|
|
110
|
+
Page.create(name: 'deleted', account: account, domain: deleted)
|
|
110
111
|
|
|
111
112
|
expect(
|
|
112
113
|
MultiTenant.with(account) do
|
|
File without changes
|
|
@@ -61,36 +61,36 @@ describe MultiTenant, 'Record finding' do
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
context 'model with has_many relation through multi-tenant model' do
|
|
64
|
-
let(:
|
|
65
|
-
let(:
|
|
64
|
+
let(:tenant1) { Account.create! name: 'Tenant 1' }
|
|
65
|
+
let(:project1) { tenant1.projects.create! }
|
|
66
66
|
|
|
67
|
-
let(:
|
|
68
|
-
let(:
|
|
67
|
+
let(:tenant2) { Account.create! name: 'Tenant 2' }
|
|
68
|
+
let(:project2) { tenant2.projects.create! }
|
|
69
69
|
|
|
70
70
|
let(:category) { Category.create! name: 'Category' }
|
|
71
71
|
|
|
72
72
|
before do
|
|
73
|
-
ProjectCategory.create! account:
|
|
74
|
-
ProjectCategory.create! account:
|
|
73
|
+
ProjectCategory.create! account: tenant1, name: '1', project: project1, category: category
|
|
74
|
+
ProjectCategory.create! account: tenant2, name: '2', project: project2, category: category
|
|
75
75
|
end
|
|
76
76
|
|
|
77
77
|
it 'can get model without creating query cache' do
|
|
78
|
-
MultiTenant.with(
|
|
79
|
-
found_category = Project.find(
|
|
78
|
+
MultiTenant.with(tenant1) do
|
|
79
|
+
found_category = Project.find(project1.id).categories.to_a.first
|
|
80
80
|
expect(found_category).to eq(category)
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
|
|
84
84
|
it 'can get model for other tenant' do
|
|
85
|
-
MultiTenant.with(
|
|
86
|
-
found_category = Project.find(
|
|
85
|
+
MultiTenant.with(tenant2) do
|
|
86
|
+
found_category = Project.find(project2.id).categories.to_a.first
|
|
87
87
|
expect(found_category).to eq(category)
|
|
88
88
|
end
|
|
89
89
|
end
|
|
90
90
|
|
|
91
91
|
it 'can get model without current_tenant' do
|
|
92
92
|
MultiTenant.without do
|
|
93
|
-
found_category = Project.find(
|
|
93
|
+
found_category = Project.find(project2.id).categories.to_a.first
|
|
94
94
|
expect(found_category).to eq(category)
|
|
95
95
|
end
|
|
96
96
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
require 'spec_helper'
|
|
2
4
|
|
|
3
5
|
describe MultiTenant, 'Record modifications' do
|
|
@@ -6,7 +8,6 @@ describe MultiTenant, 'Record modifications' do
|
|
|
6
8
|
let(:project) { Project.create! name: 'something', account: account }
|
|
7
9
|
let(:project2) { Project.create! name: 'something2', account: account2, id: project.id }
|
|
8
10
|
|
|
9
|
-
|
|
10
11
|
it 'includes the tenant_id in DELETEs when using object.destroy' do
|
|
11
12
|
# two records with same id but different account_id
|
|
12
13
|
# when doing project.destroy it should delete only the current one
|
|
@@ -16,7 +17,7 @@ describe MultiTenant, 'Record modifications' do
|
|
|
16
17
|
expect(project2.account).to eq(account2)
|
|
17
18
|
expect(project.id).to eq(project2.id)
|
|
18
19
|
|
|
19
|
-
MultiTenant.without
|
|
20
|
+
MultiTenant.without do
|
|
20
21
|
expect(Project.count).to eq(2)
|
|
21
22
|
project.destroy
|
|
22
23
|
expect(Project.count).to eq(1)
|
|
@@ -28,7 +29,6 @@ describe MultiTenant, 'Record modifications' do
|
|
|
28
29
|
MultiTenant.with(account2) do
|
|
29
30
|
expect(Project.where(id: project2.id).first).to be_present
|
|
30
31
|
end
|
|
31
|
-
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
it 'includes the tenant_id in DELETEs when using object.delete' do
|
|
@@ -40,7 +40,7 @@ describe MultiTenant, 'Record modifications' do
|
|
|
40
40
|
expect(project2.account).to eq(account2)
|
|
41
41
|
expect(project.id).to eq(project2.id)
|
|
42
42
|
|
|
43
|
-
MultiTenant.without
|
|
43
|
+
MultiTenant.without do
|
|
44
44
|
expect(Project.count).to eq(2)
|
|
45
45
|
project.delete
|
|
46
46
|
expect(Project.count).to eq(1)
|
|
@@ -5,29 +5,29 @@ require 'activerecord-multi-tenant/sidekiq'
|
|
|
5
5
|
describe MultiTenant, 'Sidekiq' do
|
|
6
6
|
let(:server) { Sidekiq::Middleware::MultiTenant::Server.new }
|
|
7
7
|
let(:account) { Account.create(name: 'test') }
|
|
8
|
-
let(:
|
|
8
|
+
let(:deleted_account) { Account.create(name: 'deleted') }
|
|
9
9
|
|
|
10
|
-
before {
|
|
10
|
+
before { deleted_account.destroy! }
|
|
11
11
|
|
|
12
12
|
describe 'server middleware' do
|
|
13
13
|
it 'sets the multitenant context when provided in message' do
|
|
14
|
-
server.call(double,{'bogus' => 'message',
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
server.call(double, { 'bogus' => 'message',
|
|
15
|
+
'multi_tenant' => { 'class' => account.class.name, 'id' => account.id } },
|
|
16
|
+
'bogus_queue') do
|
|
17
17
|
expect(MultiTenant.current_tenant).to eq(account)
|
|
18
18
|
end
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
it 'sets the multitenant context (id) even if tenant not found' do
|
|
22
|
-
server.call(double,{'bogus' => 'message',
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
expect(MultiTenant.current_tenant).to eq(
|
|
22
|
+
server.call(double, { 'bogus' => 'message',
|
|
23
|
+
'multi_tenant' => { 'class' => deleted_account.class.name, 'id' => deleted_account.id } },
|
|
24
|
+
'bogus_queue') do
|
|
25
|
+
expect(MultiTenant.current_tenant).to eq(deleted_account.id)
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
it 'does not set the multitenant context when no tenant provided' do
|
|
30
|
-
server.call(double, {'bogus' => 'message'}, 'bogus_queue') do
|
|
30
|
+
server.call(double, { 'bogus' => 'message' }, 'bogus_queue') do
|
|
31
31
|
expect(MultiTenant.current_tenant).to be_nil
|
|
32
32
|
end
|
|
33
33
|
end
|
data/spec/database.yml
CHANGED
|
File without changes
|
data/spec/schema.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
# Resets the database, except when we are only running a specific spec
|
|
2
4
|
ARGV.grep(/\w+_spec\.rb/).empty? && ActiveRecord::Schema.define(version: 1) do
|
|
3
5
|
enable_extension_on_all_nodes 'uuid-ossp'
|
|
@@ -7,6 +9,7 @@ ARGV.grep(/\w+_spec\.rb/).empty? && ActiveRecord::Schema.define(version: 1) do
|
|
|
7
9
|
t.column :name, :string
|
|
8
10
|
t.column :subdomain, :string
|
|
9
11
|
t.column :domain, :string
|
|
12
|
+
t.column :password, :string
|
|
10
13
|
end
|
|
11
14
|
|
|
12
15
|
create_table :projects, force: true, partition_key: :account_id do |t|
|
|
@@ -27,6 +30,17 @@ ARGV.grep(/\w+_spec\.rb/).empty? && ActiveRecord::Schema.define(version: 1) do
|
|
|
27
30
|
t.column :completed, :boolean
|
|
28
31
|
end
|
|
29
32
|
|
|
33
|
+
create_table :managers_tasks, force: true, partition_key: :account_id do |t|
|
|
34
|
+
t.column :account_id, :integer
|
|
35
|
+
t.column :manager_id, :integer
|
|
36
|
+
t.column :task_id, :integer
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
create_table :managers_projects, force: true do |t|
|
|
40
|
+
t.column :project_id, :integer
|
|
41
|
+
t.column :manager_id, :integer
|
|
42
|
+
end
|
|
43
|
+
|
|
30
44
|
create_table :sub_tasks, force: true, partition_key: :account_id do |t|
|
|
31
45
|
t.column :account_id, :integer
|
|
32
46
|
t.column :name, :string
|
|
@@ -152,6 +166,7 @@ class Project < ActiveRecord::Base
|
|
|
152
166
|
|
|
153
167
|
has_many :project_categories
|
|
154
168
|
has_many :categories, through: :project_categories
|
|
169
|
+
has_and_belongs_to_many :managers
|
|
155
170
|
|
|
156
171
|
validates_uniqueness_of :name, scope: [:account]
|
|
157
172
|
end
|
|
@@ -159,12 +174,15 @@ end
|
|
|
159
174
|
class Manager < ActiveRecord::Base
|
|
160
175
|
multi_tenant :account
|
|
161
176
|
belongs_to :project
|
|
177
|
+
has_and_belongs_to_many :tasks, { tenant_column: :account_id, tenant_enabled: true,
|
|
178
|
+
tenant_class_name: 'Account' }
|
|
162
179
|
end
|
|
163
180
|
|
|
164
181
|
class Task < ActiveRecord::Base
|
|
165
182
|
multi_tenant :account
|
|
166
183
|
belongs_to :project
|
|
167
184
|
has_many :sub_tasks
|
|
185
|
+
has_and_belongs_to_many :managers, tenant_column: :account_id, tenant_enabled: true
|
|
168
186
|
|
|
169
187
|
validates_uniqueness_of :name
|
|
170
188
|
end
|
|
@@ -216,7 +234,7 @@ end
|
|
|
216
234
|
class Comment < ActiveRecord::Base
|
|
217
235
|
multi_tenant :account
|
|
218
236
|
belongs_to :commentable, polymorphic: true
|
|
219
|
-
belongs_to :task, -> { where(comments: { commentable_type: 'Task'
|
|
237
|
+
belongs_to :task, -> { where(comments: { commentable_type: 'Task' }) }, foreign_key: 'commentable_id'
|
|
220
238
|
end
|
|
221
239
|
|
|
222
240
|
class Organization < ActiveRecord::Base
|
|
@@ -229,7 +247,7 @@ class UuidRecord < ActiveRecord::Base
|
|
|
229
247
|
end
|
|
230
248
|
|
|
231
249
|
class Category < ActiveRecord::Base
|
|
232
|
-
has_many
|
|
250
|
+
has_many :project_categories
|
|
233
251
|
has_many :projects, through: :project_categories
|
|
234
252
|
end
|
|
235
253
|
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,17 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
2
4
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
|
3
5
|
|
|
6
|
+
# Codecov is enabled when CI is set to true
|
|
7
|
+
if ENV['CI'] == 'true'
|
|
8
|
+
puts 'Enabling Simplecov to upload code coverage results to codecov.io'
|
|
9
|
+
require 'simplecov'
|
|
10
|
+
SimpleCov.start 'rails' do
|
|
11
|
+
add_filter '/test/' # Exclude test directory from coverage
|
|
12
|
+
add_filter '/spec/' # Exclude spec directory from coverage
|
|
13
|
+
add_filter '/config/' # Exclude config directory from coverage
|
|
14
|
+
|
|
15
|
+
# Add any additional filters or exclusions if needed
|
|
16
|
+
# add_filter '/other_directory/'
|
|
17
|
+
|
|
18
|
+
add_group 'Lib', '/lib' # Include the lib directory for coverage
|
|
19
|
+
puts "Tracked files: #{SimpleCov.tracked_files}"
|
|
20
|
+
end
|
|
21
|
+
SimpleCov.minimum_coverage 80
|
|
22
|
+
|
|
23
|
+
require 'simplecov-cobertura'
|
|
24
|
+
SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter
|
|
25
|
+
end
|
|
26
|
+
|
|
4
27
|
require 'active_record/railtie'
|
|
5
28
|
require 'action_controller/railtie'
|
|
6
29
|
require 'rspec/rails'
|
|
7
30
|
|
|
8
|
-
|
|
31
|
+
module MultiTenantTest
|
|
32
|
+
class Application < Rails::Application; end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Specifies columns which shouldn't be exposed while calling #inspect.
|
|
36
|
+
ActiveSupport.on_load(:active_record) do
|
|
37
|
+
self.filter_attributes += MultiTenantTest::Application.config.filter_parameters
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
require 'activerecord_multi_tenant'
|
|
41
|
+
|
|
42
|
+
# It's necessary for testing the filtering of senstive column values in ActiveRecord.
|
|
43
|
+
# Refer to "describe 'inspect method filters senstive column values'"
|
|
44
|
+
#
|
|
45
|
+
# To verify that ActiveSupport.on_load(:active_record) is not being unnecessarily invoked,
|
|
46
|
+
# this line should be placed after "require 'activerecord_multi_tenant'" and before ActiveRecord::Base is called.
|
|
47
|
+
MultiTenantTest::Application.config.filter_parameters = [:password]
|
|
9
48
|
|
|
10
49
|
require 'bundler'
|
|
11
50
|
Bundler.require(:default, :development)
|
|
51
|
+
require_relative './support/format_sql'
|
|
12
52
|
|
|
13
|
-
dbconfig = YAML
|
|
14
|
-
ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__),
|
|
53
|
+
dbconfig = YAML.safe_load(IO.read(File.join(File.dirname(__FILE__), 'database.yml')))
|
|
54
|
+
ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), 'debug.log'))
|
|
15
55
|
ActiveRecord::Base.establish_connection(dbconfig['test'])
|
|
16
56
|
|
|
17
57
|
RSpec.configure do |config|
|
|
@@ -25,9 +65,6 @@ RSpec.configure do |config|
|
|
|
25
65
|
|
|
26
66
|
config.before(:suite) do
|
|
27
67
|
MultiTenant::FastTruncate.run
|
|
28
|
-
|
|
29
|
-
# Keep this here until https://github.com/citusdata/citus/issues/1236 is fixed
|
|
30
|
-
MultiTenant.enable_with_lock_workaround
|
|
31
68
|
end
|
|
32
69
|
|
|
33
70
|
config.after(:each) do
|
|
@@ -35,17 +72,8 @@ RSpec.configure do |config|
|
|
|
35
72
|
end
|
|
36
73
|
end
|
|
37
74
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
MultiTenantTest::Application.config.secret_token = 'x' * 40
|
|
43
|
-
MultiTenantTest::Application.config.secret_key_base = 'y' * 40
|
|
44
|
-
|
|
45
|
-
def uses_prepared_statements?
|
|
46
|
-
ActiveRecord::Base.connection.prepared_statements
|
|
47
|
-
end
|
|
48
|
-
|
|
75
|
+
# rubocop:disable Lint/UnusedMethodArgument
|
|
76
|
+
# changing the name of the parameter breaks tests
|
|
49
77
|
def with_belongs_to_required_by_default(&block)
|
|
50
78
|
default_value = ActiveRecord::Base.belongs_to_required_by_default
|
|
51
79
|
ActiveRecord::Base.belongs_to_required_by_default = true
|
|
@@ -53,4 +81,5 @@ def with_belongs_to_required_by_default(&block)
|
|
|
53
81
|
ensure
|
|
54
82
|
ActiveRecord::Base.belongs_to_required_by_default = default_value
|
|
55
83
|
end
|
|
84
|
+
# rubocop:enable Lint/UnusedMethodArgument
|
|
56
85
|
require 'schema'
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'anbt-sql-formatter/formatter'
|
|
4
|
+
|
|
5
|
+
module SQLFormatter
|
|
6
|
+
def format_sql(sql)
|
|
7
|
+
rule = AnbtSql::Rule.new
|
|
8
|
+
rule.keyword = AnbtSql::Rule::KEYWORD_UPPER_CASE
|
|
9
|
+
%w[count sum substr date].each do |func_name|
|
|
10
|
+
rule.function_names << func_name.upcase
|
|
11
|
+
end
|
|
12
|
+
rule.indent_string = ' '
|
|
13
|
+
formatter = AnbtSql::Formatter.new(rule)
|
|
14
|
+
formatter.format(sql.dup)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
RSpec.configure do |config|
|
|
19
|
+
config.include SQLFormatter
|
|
20
|
+
end
|