activerecord-multi-tenant 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/active-record-multi-tenant-tests.yml +80 -0
  3. data/.gitignore +6 -0
  4. data/.readthedocs.yaml +15 -0
  5. data/.rspec +0 -0
  6. data/.rubocop.yml +51 -0
  7. data/Appraisals +0 -0
  8. data/CHANGELOG.md +6 -0
  9. data/Gemfile +3 -1
  10. data/LICENSE +0 -0
  11. data/README.md +2 -1
  12. data/Rakefile +1 -1
  13. data/activerecord-multi-tenant.gemspec +28 -22
  14. data/docker-compose.yml +24 -18
  15. data/docs/.gitignore +3 -0
  16. data/docs/Makefile +28 -0
  17. data/docs/api-reference.sh +10 -0
  18. data/docs/requirements.in +4 -0
  19. data/docs/requirements.txt +62 -0
  20. data/docs/source/_static/api-reference/ActiveRecord/Associations/Association.html +285 -0
  21. data/docs/source/_static/api-reference/ActiveRecord/Associations/ClassMethods.html +255 -0
  22. data/docs/source/_static/api-reference/ActiveRecord/Associations.html +117 -0
  23. data/docs/source/_static/api-reference/ActiveRecord/ConnectionAdapters/SchemaStatements.html +232 -0
  24. data/docs/source/_static/api-reference/ActiveRecord/ConnectionAdapters.html +126 -0
  25. data/docs/source/_static/api-reference/ActiveRecord/QueryMethods.html +336 -0
  26. data/docs/source/_static/api-reference/ActiveRecord/SchemaDumper.html +121 -0
  27. data/docs/source/_static/api-reference/ActiveRecord.html +130 -0
  28. data/docs/source/_static/api-reference/MultiTenant/ArelTenantVisitor.html +755 -0
  29. data/docs/source/_static/api-reference/MultiTenant/ArelVisitorsDepthFirst.html +208 -0
  30. data/docs/source/_static/api-reference/MultiTenant/BaseTenantEnforcementClause.html +462 -0
  31. data/docs/source/_static/api-reference/MultiTenant/Context.html +659 -0
  32. data/docs/source/_static/api-reference/MultiTenant/ControllerExtensions.html +202 -0
  33. data/docs/source/_static/api-reference/MultiTenant/CopyFromClient.html +186 -0
  34. data/docs/source/_static/api-reference/MultiTenant/CopyFromClientHelper.html +362 -0
  35. data/docs/source/_static/api-reference/MultiTenant/Current.html +124 -0
  36. data/docs/source/_static/api-reference/MultiTenant/DatabaseStatements.html +366 -0
  37. data/docs/source/_static/api-reference/MultiTenant/FastTruncate.html +226 -0
  38. data/docs/source/_static/api-reference/MultiTenant/MigrationExtensions.html +554 -0
  39. data/docs/source/_static/api-reference/MultiTenant/MissingTenantError.html +124 -0
  40. data/docs/source/_static/api-reference/MultiTenant/ModelExtensionsClassMethods.html +492 -0
  41. data/docs/source/_static/api-reference/MultiTenant/QueryMonitor.html +257 -0
  42. data/docs/source/_static/api-reference/MultiTenant/Table.html +419 -0
  43. data/docs/source/_static/api-reference/MultiTenant/TenantEnforcementClause.html +148 -0
  44. data/docs/source/_static/api-reference/MultiTenant/TenantIsImmutable.html +135 -0
  45. data/docs/source/_static/api-reference/MultiTenant/TenantJoinEnforcementClause.html +310 -0
  46. data/docs/source/_static/api-reference/MultiTenant/TenantValueVisitor.html +239 -0
  47. data/docs/source/_static/api-reference/MultiTenant.html +1454 -0
  48. data/docs/source/_static/api-reference/MultiTenantFindBy.html +180 -0
  49. data/docs/source/_static/api-reference/Sidekiq/Client.html +302 -0
  50. data/docs/source/_static/api-reference/Sidekiq/Middleware/MultiTenant/Client.html +217 -0
  51. data/docs/source/_static/api-reference/Sidekiq/Middleware/MultiTenant/Server.html +219 -0
  52. data/docs/source/_static/api-reference/Sidekiq/Middleware/MultiTenant.html +126 -0
  53. data/docs/source/_static/api-reference/Sidekiq.html +126 -0
  54. data/docs/source/_static/api-reference/_index.html +399 -0
  55. data/docs/source/_static/api-reference/class_list.html +51 -0
  56. data/docs/source/_static/api-reference/css/common.css +1 -0
  57. data/docs/source/_static/api-reference/css/full_list.css +58 -0
  58. data/docs/source/_static/api-reference/css/style.css +497 -0
  59. data/docs/source/_static/api-reference/file.README.html +167 -0
  60. data/docs/source/_static/api-reference/file_list.html +56 -0
  61. data/docs/source/_static/api-reference/frames.html +17 -0
  62. data/docs/source/_static/api-reference/index.html +167 -0
  63. data/docs/source/_static/api-reference/js/app.js +314 -0
  64. data/docs/source/_static/api-reference/js/full_list.js +216 -0
  65. data/docs/source/_static/api-reference/js/jquery.js +4 -0
  66. data/docs/source/_static/api-reference/method_list.html +715 -0
  67. data/docs/source/_static/api-reference/top-level-namespace.html +126 -0
  68. data/docs/source/_templates/.gitignore +4 -0
  69. data/docs/source/api-reference.rst +8 -0
  70. data/docs/source/appendix.rst +26 -0
  71. data/docs/source/changelog.rst +8 -0
  72. data/docs/source/community-and-support.rst +26 -0
  73. data/docs/source/conf.py +30 -0
  74. data/docs/source/contributing.rst +70 -0
  75. data/docs/source/getting-started.rst +37 -0
  76. data/docs/source/guides-and-tutorials.rst +129 -0
  77. data/docs/source/index.rst +54 -0
  78. data/docs/source/introduction.rst +33 -0
  79. data/docs/source/license.rst +22 -0
  80. data/docs/source/troubleshooting.rst +41 -0
  81. data/docs/source/usage-guide.rst +59 -0
  82. data/lib/activerecord-multi-tenant/arel_visitors_depth_first.rb +183 -174
  83. data/lib/activerecord-multi-tenant/controller_extensions.rb +15 -4
  84. data/lib/activerecord-multi-tenant/copy_from_client.rb +4 -0
  85. data/lib/activerecord-multi-tenant/fast_truncate.rb +4 -2
  86. data/lib/activerecord-multi-tenant/habtm.rb +50 -0
  87. data/lib/activerecord-multi-tenant/migrations.rb +18 -8
  88. data/lib/activerecord-multi-tenant/model_extensions.rb +78 -37
  89. data/lib/activerecord-multi-tenant/multi_tenant.rb +40 -21
  90. data/lib/activerecord-multi-tenant/query_monitor.rb +21 -5
  91. data/lib/activerecord-multi-tenant/query_rewriter.rb +111 -80
  92. data/lib/activerecord-multi-tenant/sidekiq.rb +31 -20
  93. data/lib/activerecord-multi-tenant/version.rb +1 -1
  94. data/lib/activerecord-multi-tenant.rb +3 -12
  95. data/lib/activerecord_multi_tenant.rb +12 -0
  96. data/spec/activerecord-multi-tenant/associations_spec.rb +21 -0
  97. data/spec/activerecord-multi-tenant/controller_extensions_spec.rb +3 -2
  98. data/spec/activerecord-multi-tenant/fast_truncate_spec.rb +8 -6
  99. data/spec/activerecord-multi-tenant/model_extensions_spec.rb +233 -153
  100. data/spec/activerecord-multi-tenant/multi_tenant_spec.rb +15 -13
  101. data/spec/activerecord-multi-tenant/query_rewriter_spec.rb +60 -59
  102. data/spec/activerecord-multi-tenant/record_callback_spec.rb +0 -0
  103. data/spec/activerecord-multi-tenant/record_finding_spec.rb +11 -11
  104. data/spec/activerecord-multi-tenant/record_modifications_spec.rb +4 -4
  105. data/spec/activerecord-multi-tenant/sidekiq_spec.rb +10 -10
  106. data/spec/database.yml +0 -0
  107. data/spec/schema.rb +20 -2
  108. data/spec/spec_helper.rb +46 -17
  109. data/spec/support/format_sql.rb +20 -0
  110. metadata +130 -25
  111. data/.github/workflows/CI.yml +0 -47
  112. data/gemfiles/.bundle/config +0 -2
  113. data/gemfiles/active_record_6.0.gemfile +0 -8
  114. data/gemfiles/active_record_6.1.gemfile +0 -8
  115. data/gemfiles/active_record_7.0.gemfile +0 -8
  116. data/gemfiles/rails_6.0.gemfile +0 -8
  117. data/gemfiles/rails_6.1.gemfile +0 -8
  118. data/gemfiles/rails_7.0.gemfile +0 -8
  119. data/lib/activerecord-multi-tenant/with_lock.rb +0 -15
  120. 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 ".load_current_tenant!" do
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 "sets and returns the loaded current_tenant" do
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 "respects `.with` lifecycle" do
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 "with a loaded current_tenant" do
35
- it "returns the tenant without fetching it" do
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 "with a nil current_tenant" do
43
- it "raises an error, as there is not enough information to load the tenant" do
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
- }.to raise_error(RuntimeError, 'MultiTenant.current_tenant must be set to load')
49
+ end.to raise_error(RuntimeError, 'MultiTenant.current_tenant must be set to load')
48
50
  end
49
51
  end
50
52
 
51
- context "without a default class set" do
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 "raises an error, as there is not enough information to load the tenant" do
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
- }.to raise_error(RuntimeError, 'Only have tenant id, and no default tenant class set')
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
- require 'spec_helper'
1
+ # frozen_string_literal: true
2
2
 
3
- describe "Query Rewriter" do
3
+ require 'spec_helper'
4
4
 
5
- context "when bulk updating" do
6
- let!(:account) { Account.create!(name: "Test Account") }
7
- let!(:project) { Project.create(name: "Project 1", account: account) }
8
- let!(:manager) { Manager.create(name: "Manager", project: project, account: account) }
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 "updates the records" do
11
- expect {
11
+ it 'updates the records' do
12
+ expect do
12
13
  MultiTenant.with(account) do
13
- Project.joins(:manager).update_all(name: "New Name")
14
+ Project.joins(:manager).update_all(name: 'New Name')
14
15
  end
15
- }.to change { project.reload.name }.from("Project 1").to("New Name")
16
+ end.to change { project.reload.name }.from('Project 1').to('New Name')
16
17
  end
17
18
 
18
- it "updates the records without a current tenant" do
19
- expect {
20
- Project.joins(:manager).update_all(name: "New Name")
21
- }.to change { project.reload.name }.from("Project 1").to("New Name")
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 "update the record" do
25
- expect {
25
+ it 'update the record' do
26
+ expect do
26
27
  MultiTenant.with(account) do
27
- project.update(name: "New Name")
28
+ project.update(name: 'New Name')
28
29
  end
29
- }.to change { project.reload.name }.from("Project 1").to("New Name")
30
+ end.to change { project.reload.name }.from('Project 1').to('New Name')
30
31
  end
31
32
 
32
- it "update the record without a current tenant" do
33
- expect {
34
- project.update(name: "New Name")
35
- }.to change { project.reload.name }.from("Project 1").to("New Name")
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 "when bulk deleting" do
40
- let!(:account) { Account.create!(name: "Test Account") }
41
- let!(:project1) { Project.create(name: "Project 1", account: account) }
42
- let!(:project2) { Project.create(name: "Project 2", account: account) }
43
- let!(:project3) { Project.create(name: "Project 3", account: account) }
44
- let!(:manager1) { Manager.create(name: "Manager 1", project: project1, account: account) }
45
- let!(:manager2) { Manager.create(name: "Manager 2", project: project2, account: account) }
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 "delete_all the records" do
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
- }.to change { Project.count }.from(3).to(1)
53
+ end.to change { Project.count }.from(3).to(1)
53
54
  end
54
55
 
55
- it "delete_all the records without a current tenant" do
56
- expect {
56
+ it 'delete_all the records without a current tenant' do
57
+ expect do
57
58
  Project.joins(:manager).delete_all
58
- }.to change { Project.count }.from(3).to(1)
59
+ end.to change { Project.count }.from(3).to(1)
59
60
  end
60
61
 
61
- it "delete the record" do
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
- }.to change { Project.count }.from(3).to(1)
68
+ end.to change { Project.count }.from(3).to(1)
68
69
  end
69
70
 
70
- it "delete the record without a current tenant" do
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
- }.to change { Project.count }.from(3).to(1)
75
+ end.to change { Project.count }.from(3).to(1)
75
76
  end
76
77
 
77
- it "destroy the record" do
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
- }.to change { Project.count }.from(3).to(1)
84
+ end.to change { Project.count }.from(3).to(1)
84
85
  end
85
86
 
86
- it "destroy the record without a current tenant" do
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
- }.to change { Project.count }.from(3).to(1)
91
+ end.to change { Project.count }.from(3).to(1)
91
92
  end
92
93
  end
93
94
 
94
- context "when update without arel" do
95
- it "can call method" do
96
- expect {
97
- ActiveRecord::Base.connection.update("SELECT 1")
98
- }.not_to raise_error
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 "when joining with a model with a default scope" do
103
- let!(:account) { Account.create!(name: "Test Account") }
103
+ context 'when joining with a model with a default scope' do
104
+ let!(:account) { Account.create!(name: 'Test Account') }
104
105
 
105
- it "fetches only records within the default scope" do
106
- alive = Domain.create(name: "alive", account: account)
107
- deleted = Domain.create(name: "deleted", deleted: true, account: account)
108
- page_in_alive_domain = Page.create(name: "alive", account: account, domain: alive)
109
- page_in_deleted_domain = Page.create(name: "deleted", account: account, domain: deleted)
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
@@ -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(:tenant_1) { Account.create! name: 'Tenant 1' }
65
- let(:project_1) { tenant_1.projects.create! }
64
+ let(:tenant1) { Account.create! name: 'Tenant 1' }
65
+ let(:project1) { tenant1.projects.create! }
66
66
 
67
- let(:tenant_2) { Account.create! name: 'Tenant 2' }
68
- let(:project_2) { tenant_2.projects.create! }
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: tenant_1, name: '1', project: project_1, category: category
74
- ProjectCategory.create! account: tenant_2, name: '2', project: project_2, category: category
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(tenant_1) do
79
- found_category = Project.find(project_1.id).categories.to_a.first
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(tenant_2) do
86
- found_category = Project.find(project_2.id).categories.to_a.first
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(project_2.id).categories.to_a.first
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() do
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() do
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(:deleted_acount) { Account.create(name: 'deleted') }
8
+ let(:deleted_account) { Account.create(name: 'deleted') }
9
9
 
10
- before { deleted_acount.destroy! }
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
- 'multi_tenant' => { 'class' => account.class.name, 'id' => account.id}},
16
- 'bogus_queue') do
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
- 'multi_tenant' => { 'class' => deleted_acount.class.name, 'id' => deleted_acount.id}},
24
- 'bogus_queue') do
25
- expect(MultiTenant.current_tenant).to eq(deleted_acount.id)
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' }) }, foreign_key: 'commentable_id'
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 :project_categories
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
- require 'activerecord-multi-tenant'
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::load(IO.read(File.join(File.dirname(__FILE__), 'database.yml')))
14
- ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
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
- module MultiTenantTest
39
- class Application < Rails::Application; end
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