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.
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