activerecord-multi-tenant 1.1.1 → 2.2.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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/CI.yml +47 -0
  3. data/.gitignore +2 -0
  4. data/.rspec +1 -0
  5. data/Appraisals +6 -22
  6. data/CHANGELOG.md +46 -0
  7. data/README.md +1 -1
  8. data/activerecord-multi-tenant.gemspec +1 -2
  9. data/gemfiles/active_record_7.0.gemfile +8 -0
  10. data/gemfiles/rails_7.0.gemfile +8 -0
  11. data/lib/activerecord-multi-tenant/copy_from_client.rb +2 -2
  12. data/lib/activerecord-multi-tenant/migrations.rb +76 -9
  13. data/lib/activerecord-multi-tenant/model_extensions.rb +31 -8
  14. data/lib/activerecord-multi-tenant/multi_tenant.rb +57 -8
  15. data/lib/activerecord-multi-tenant/query_rewriter.rb +15 -11
  16. data/lib/activerecord-multi-tenant/sidekiq.rb +21 -1
  17. data/lib/activerecord-multi-tenant/version.rb +1 -1
  18. data/lib/activerecord-multi-tenant.rb +0 -1
  19. data/spec/activerecord-multi-tenant/associations_spec.rb +21 -0
  20. data/spec/activerecord-multi-tenant/model_extensions_spec.rb +186 -54
  21. data/spec/activerecord-multi-tenant/query_rewriter_spec.rb +17 -0
  22. data/spec/activerecord-multi-tenant/record_modifications_spec.rb +19 -0
  23. data/spec/activerecord-multi-tenant/sidekiq_spec.rb +11 -0
  24. data/spec/schema.rb +50 -4
  25. data/spec/spec_helper.rb +7 -0
  26. metadata +13 -33
  27. data/.travis.yml +0 -34
  28. data/Gemfile.lock +0 -199
  29. data/gemfiles/active_record_5.2.gemfile +0 -16
  30. data/gemfiles/active_record_5.2.gemfile.lock +0 -188
  31. data/gemfiles/active_record_6.0.gemfile.lock +0 -198
  32. data/gemfiles/active_record_6.1.gemfile.lock +0 -198
  33. data/gemfiles/rails_5.2.gemfile +0 -16
  34. data/gemfiles/rails_5.2.gemfile.lock +0 -188
  35. data/gemfiles/rails_6.0.gemfile.lock +0 -198
  36. data/gemfiles/rails_6.1.gemfile.lock +0 -198
  37. data/lib/activerecord-multi-tenant/persistence_extension.rb +0 -13
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe MultiTenant, 'Association methods' do
4
+ let(:account1) { Account.create! name: 'test1' }
5
+ let(:account2) { Account.create! name: 'test2' }
6
+ let(:project1) { Project.create! name: 'something1', account: account1 }
7
+ let(:project2) { Project.create! name: 'something2', account: account2, id: project1.id }
8
+ let(:task1) { Task.create! name: 'task1', project: project1, account: account1 }
9
+ let(:task2) { Task.create! name: 'task2', project: project2, account: account2, id: task1.id }
10
+
11
+ context 'include the tenant_id in queries and' do
12
+ it 'creates a task with correct account_id' do
13
+ expect(project2.tasks.create(name: 'task3').account_id).to eq(account2.id)
14
+ end
15
+ it 'return correct account_id' do
16
+ expect(task1.project.account_id).to_not eq(task2.project.account_id) # belongs_to
17
+ expect(project2.tasks.count).to eq(1)
18
+ expect(project2.tasks.first.account_id).to eq(account2.id) # has_many
19
+ end
20
+ end
21
+ end
@@ -70,6 +70,65 @@ describe MultiTenant do
70
70
  it { expect(@partition_key_not_model_task.non_model_id).to be 77 }
71
71
  end
72
72
 
73
+
74
+ describe 'Tenant model with a nonstandard class name' do
75
+ let(:account_klass) do
76
+ Class.new(ActiveRecord::Base) do
77
+ self.table_name = 'account'
78
+ def self.name
79
+ 'UserAccount'
80
+ end
81
+
82
+ multi_tenant(:account)
83
+ end
84
+ end
85
+ it "does not register the tenant model" do
86
+ expect(MultiTenant).not_to receive(:register_multi_tenant_model)
87
+ account_klass
88
+ end
89
+ end
90
+
91
+ describe 'Changes table_name after multi_tenant called' do
92
+ before do
93
+ account_klass.has_many(:posts, anonymous_class: post_klass)
94
+ post_klass.belongs_to(:account, anonymous_class: account_klass)
95
+
96
+ @account1 = account_klass.create! name: 'foo'
97
+ @account2 = account_klass.create! name: 'bar'
98
+
99
+ @post1 = @account1.posts.create! name: 'foobar'
100
+ @post2 = @account2.posts.create! name: 'baz'
101
+
102
+ MultiTenant.current_tenant = @account1
103
+ @posts = post_klass.all
104
+ end
105
+
106
+ let(:account_klass) do
107
+ Class.new(Account) do
108
+ def self.name
109
+ 'Account'
110
+ end
111
+ end
112
+ end
113
+
114
+ let(:post_klass) do
115
+ Class.new(ActiveRecord::Base) do
116
+ self.table_name = 'unknown'
117
+
118
+ multi_tenant(:account)
119
+
120
+ self.table_name = 'posts'
121
+
122
+ def self.name
123
+ 'Post'
124
+ end
125
+ end
126
+ end
127
+
128
+ it { expect(@posts.length).to eq(1) }
129
+ it { expect(@posts).to eq([@post1]) }
130
+ end
131
+
73
132
  # Scoping models
74
133
  describe 'Project.all should be scoped to the current tenant if set' do
75
134
  before do
@@ -144,6 +203,22 @@ describe MultiTenant do
144
203
  end
145
204
  end
146
205
 
206
+ it 'handles belongs_to with optional: true' do
207
+ record = OptionalSubTask.create(sub_task_id: sub_task.id)
208
+ expect(record.reload.sub_task).to eq(sub_task)
209
+ expect(record.account_id).to eq(nil)
210
+ end
211
+
212
+ it 'handles changing tenant from nil to a value' do
213
+ record = OptionalSubTask.create(sub_task_id: sub_task.id)
214
+ expect(record.reload.sub_task).to eq(sub_task)
215
+ expect(record.account_id).to eq(nil)
216
+
217
+ record.account = account
218
+ record.save!
219
+ expect(record.reload.account_id).to eq(account.id)
220
+ end
221
+
147
222
  it 'handles has_many through' do
148
223
  MultiTenant.with(account) do
149
224
  expect(project.sub_tasks).to eq [sub_task]
@@ -234,6 +309,55 @@ describe MultiTenant do
234
309
  end
235
310
  end
236
311
 
312
+ # Joins
313
+ describe 'joins for models' do
314
+ context 'for models with where condition in associations' do
315
+ let(:account) { Account.create!(name: 'Account 1') }
316
+
317
+ it 'should add tenant condition to the queries when tenant is set' do
318
+ expected_join_sql = <<-SQL.strip
319
+ SELECT "comments".* FROM "comments" INNER JOIN "tasks" ON "tasks"."id" = "comments"."commentable_id" AND "comments"."commentable_type" = 'Task' AND "tasks"."account_id" = 1 WHERE "comments"."account_id" = 1
320
+ SQL
321
+
322
+ MultiTenant.with(account) do
323
+ expect(Comment.joins(:task).to_sql).to eq(expected_join_sql)
324
+ end
325
+ end
326
+
327
+ it 'should add tenant condition to the queries when tenant is not set' do
328
+ MultiTenant.without do
329
+ expected_join_sql = <<-SQL.strip
330
+ SELECT "comments".* FROM "comments" INNER JOIN "tasks" ON "tasks"."id" = "comments"."commentable_id" AND "comments"."commentable_type" = 'Task' AND "comments"."account_id" = "tasks"."account_id"
331
+ SQL
332
+ expect(Comment.joins(:task).to_sql).to eq(expected_join_sql)
333
+ end
334
+ end
335
+ end
336
+
337
+ context 'for models with default associations' do
338
+ let(:account) { Account.create!(name: 'Account 1') }
339
+
340
+ it 'should add tenant condition to the queries when tenant is set' do
341
+ expected_join_sql = <<-SQL.strip
342
+ SELECT "projects".* FROM "projects" INNER JOIN "tasks" ON "tasks"."project_id" = "projects"."id" AND "tasks"."account_id" = 1 WHERE "projects"."account_id" = 1
343
+ SQL
344
+
345
+ MultiTenant.with(account) do
346
+ expect(Project.joins(:tasks).to_sql).to eq(expected_join_sql)
347
+ end
348
+ end
349
+
350
+ it 'should add tenant condition to the queries when tenant is not set' do
351
+ MultiTenant.without do
352
+ expected_join_sql = <<-SQL.strip
353
+ SELECT "projects".* FROM "projects" INNER JOIN "tasks" ON "tasks"."project_id" = "projects"."id" AND "projects"."account_id" = "tasks"."account_id"
354
+ SQL
355
+ expect(Project.joins(:tasks).to_sql).to eq(expected_join_sql)
356
+ end
357
+ end
358
+ end
359
+ end
360
+
237
361
  # ::with
238
362
  describe "::with" do
239
363
  it "should set current_tenant to the specified tenant inside the block" do
@@ -348,9 +472,12 @@ describe MultiTenant do
348
472
  end
349
473
 
350
474
  it "applies the team_id conditions in the where clause" do
351
- expected_sql = <<-sql
352
- SELECT "sub_tasks".* FROM "sub_tasks" INNER JOIN "tasks" ON "sub_tasks"."task_id" = "tasks"."id" AND "sub_tasks"."account_id" = "tasks"."account_id" WHERE "tasks"."account_id" = 1 AND "sub_tasks"."account_id" = 1 AND "tasks"."project_id" = 1
353
- sql
475
+ option1 = <<-sql.strip
476
+ SELECT "sub_tasks".* FROM "sub_tasks" INNER JOIN "tasks" ON "sub_tasks"."task_id" = "tasks"."id" AND "tasks"."account_id" = "sub_tasks"."account_id" WHERE "tasks"."project_id" = 1 AND "sub_tasks"."account_id" = 1 AND "tasks"."account_id" = 1
477
+ sql
478
+ option2 = <<-sql.strip
479
+ SELECT "sub_tasks".* FROM "sub_tasks" INNER JOIN "tasks" ON "sub_tasks"."task_id" = "tasks"."id" AND "tasks"."account_id" = "sub_tasks"."account_id" WHERE "sub_tasks"."account_id" = 1 AND "tasks"."project_id" = 1 AND "tasks"."account_id" = 1
480
+ sql
354
481
 
355
482
  account1 = Account.create! name: 'Account 1'
356
483
 
@@ -358,13 +485,13 @@ describe MultiTenant do
358
485
  project1 = Project.create! name: 'Project 1'
359
486
  task1 = Task.create! name: 'Task 1', project: project1
360
487
  subtask1 = SubTask.create! task: task1
361
- expect(project1.sub_tasks.to_sql).to eq(expected_sql.strip)
488
+ expect(project1.sub_tasks.to_sql).to eq(option1).or(eq(option2))
362
489
  expect(project1.sub_tasks).to include(subtask1)
363
490
  end
364
491
 
365
492
  MultiTenant.without do
366
493
  expected_sql = <<-sql
367
- SELECT "sub_tasks".* FROM "sub_tasks" INNER JOIN "tasks" ON "sub_tasks"."task_id" = "tasks"."id" AND "sub_tasks"."account_id" = "tasks"."account_id" WHERE "tasks"."project_id" = 1
494
+ SELECT "sub_tasks".* FROM "sub_tasks" INNER JOIN "tasks" ON "sub_tasks"."task_id" = "tasks"."id" AND "tasks"."account_id" = "sub_tasks"."account_id" WHERE "tasks"."project_id" = 1
368
495
  sql
369
496
 
370
497
  project = Project.first
@@ -373,9 +500,13 @@ describe MultiTenant do
373
500
  end
374
501
 
375
502
  it "tests joins between distributed and reference table" do
376
- expected_sql = <<-sql
377
- SELECT "categories".* FROM "categories" INNER JOIN "project_categories" ON "categories"."id" = "project_categories"."category_id" WHERE "project_categories"."account_id" = 1 AND "project_categories"."project_id" = 1
378
- sql
503
+ option1 = <<-sql.strip
504
+ SELECT "categories".* FROM "categories" INNER JOIN "project_categories" ON "categories"."id" = "project_categories"."category_id" WHERE "project_categories"."project_id" = 1 AND "project_categories"."account_id" = 1
505
+ sql
506
+ option2 = <<-sql.strip
507
+ SELECT "categories".* FROM "categories" INNER JOIN "project_categories" ON "categories"."id" = "project_categories"."category_id" WHERE "project_categories"."account_id" = 1 AND "project_categories"."project_id" = 1
508
+ sql
509
+
379
510
  account1 = Account.create! name: 'Account 1'
380
511
  category1 = Category.create! name: 'Category 1'
381
512
 
@@ -383,7 +514,7 @@ describe MultiTenant do
383
514
  project1 = Project.create! name: 'Project 1'
384
515
  projectcategory = ProjectCategory.create! name: 'project cat 1', project: project1, category: category1
385
516
 
386
- expect(project1.categories.to_sql).to eq(expected_sql.strip)
517
+ expect(project1.categories.to_sql).to eq(option1).or(eq(option2))
387
518
  expect(project1.categories).to include(category1)
388
519
  expect(project1.project_categories).to include(projectcategory)
389
520
  end
@@ -398,7 +529,7 @@ describe MultiTenant do
398
529
  expect(project.categories).to include(category1)
399
530
 
400
531
  expected_sql = <<-sql
401
- SELECT "projects".* FROM "projects" INNER JOIN "project_categories" ON "project_categories"."project_id" = "projects"."id" AND "project_categories"."account_id" = "projects"."account_id" INNER JOIN "categories" ON "categories"."id" = "project_categories"."category_id" WHERE "projects"."account_id" = 1
532
+ SELECT "projects".* FROM "projects" INNER JOIN "project_categories" ON "project_categories"."project_id" = "projects"."id" AND "projects"."account_id" = "project_categories"."account_id" INNER JOIN "categories" ON "categories"."id" = "project_categories"."category_id" WHERE "projects"."account_id" = 1
402
533
  sql
403
534
 
404
535
  expect(Project.where(account_id: 1).joins(:categories).to_sql).to eq(expected_sql.strip)
@@ -412,21 +543,18 @@ describe MultiTenant do
412
543
  account1 = Account.create! name: 'Account 1'
413
544
  category1 = Category.create! name: 'Category 1'
414
545
 
415
- expected_sql = if uses_prepared_statements? && (ActiveRecord::VERSION::MAJOR == 5 || (ActiveRecord::VERSION::MAJOR == 6 && ActiveRecord::VERSION::MINOR >= 1))
416
- <<-sql
417
- SELECT "projects"."id" AS t0_r0, "projects"."account_id" AS t0_r1, "projects"."name" AS t0_r2, "categories"."id" AS t1_r0, "categories"."name" AS t1_r1 FROM "projects" LEFT OUTER JOIN "project_categories" ON "project_categories"."project_id" = "projects"."id" AND "project_categories"."account_id" = 1 AND "projects"."account_id" = 1 LEFT OUTER JOIN "categories" ON "categories"."id" = "project_categories"."category_id" AND "project_categories"."account_id" = 1 WHERE "projects"."account_id" = 1
418
- sql
419
- else
420
- <<-sql
421
- SELECT "projects"."id" AS t0_r0, "projects"."account_id" AS t0_r1, "projects"."name" AS t0_r2, "categories"."id" AS t1_r0, "categories"."name" AS t1_r1 FROM "projects" LEFT OUTER JOIN "project_categories" ON "project_categories"."account_id" = 1 AND "project_categories"."project_id" = "projects"."id" AND "projects"."account_id" = 1 LEFT OUTER JOIN "categories" ON "categories"."id" = "project_categories"."category_id" AND "project_categories"."account_id" = 1 WHERE "projects"."account_id" = 1
422
- sql
423
- end
546
+ option1 = <<-sql.strip
547
+ SELECT "projects"."id" AS t0_r0, "projects"."account_id" AS t0_r1, "projects"."name" AS t0_r2, "categories"."id" AS t1_r0, "categories"."name" AS t1_r1 FROM "projects" LEFT OUTER JOIN "project_categories" ON "project_categories"."project_id" = "projects"."id" AND "project_categories"."account_id" = 1 AND "projects"."account_id" = 1 LEFT OUTER JOIN "categories" ON "categories"."id" = "project_categories"."category_id" AND "project_categories"."account_id" = 1 WHERE "projects"."account_id" = 1
548
+ sql
549
+ option2 = <<-sql.strip
550
+ SELECT "projects"."id" AS t0_r0, "projects"."account_id" AS t0_r1, "projects"."name" AS t0_r2, "categories"."id" AS t1_r0, "categories"."name" AS t1_r1 FROM "projects" LEFT OUTER JOIN "project_categories" ON "project_categories"."account_id" = 1 AND "project_categories"."project_id" = "projects"."id" AND "projects"."account_id" = 1 LEFT OUTER JOIN "categories" ON "categories"."id" = "project_categories"."category_id" AND "project_categories"."account_id" = 1 WHERE "projects"."account_id" = 1
551
+ sql
424
552
 
425
553
  MultiTenant.with(account1) do
426
554
  project1 = Project.create! name: 'Project 1'
427
555
  projectcategory = ProjectCategory.create! name: 'project cat 1', project: project1, category: category1
428
556
 
429
- expect(Project.eager_load(:categories).to_sql).to eq(expected_sql.strip)
557
+ expect(Project.eager_load(:categories).to_sql).to eq(option1).or(eq(option2))
430
558
 
431
559
  project = Project.eager_load(:categories).first
432
560
  expect(project.categories).to include(category1)
@@ -435,7 +563,7 @@ describe MultiTenant do
435
563
 
436
564
  MultiTenant.without do
437
565
  expected_sql = <<-sql
438
- SELECT "projects"."id" AS t0_r0, "projects"."account_id" AS t0_r1, "projects"."name" AS t0_r2, "categories"."id" AS t1_r0, "categories"."name" AS t1_r1 FROM "projects" LEFT OUTER JOIN "project_categories" ON "project_categories"."project_id" = "projects"."id" AND "project_categories"."account_id" = "projects"."account_id" LEFT OUTER JOIN "categories" ON "categories"."id" = "project_categories"."category_id" WHERE "projects"."account_id" = 1
566
+ SELECT "projects"."id" AS t0_r0, "projects"."account_id" AS t0_r1, "projects"."name" AS t0_r2, "categories"."id" AS t1_r0, "categories"."name" AS t1_r1 FROM "projects" LEFT OUTER JOIN "project_categories" ON "project_categories"."project_id" = "projects"."id" AND "projects"."account_id" = "project_categories"."account_id" LEFT OUTER JOIN "categories" ON "categories"."id" = "project_categories"."category_id" WHERE "projects"."account_id" = 1
439
567
  sql
440
568
 
441
569
  expect(Project.where(account_id: 1).eager_load(:categories).to_sql).to eq(expected_sql.strip)
@@ -451,26 +579,23 @@ describe MultiTenant do
451
579
  category1 = Category.create! name: 'Category 1'
452
580
 
453
581
  MultiTenant.with(account1) do
454
- expected_sql = if uses_prepared_statements? && (ActiveRecord::VERSION::MAJOR == 5 || (ActiveRecord::VERSION::MAJOR == 6 && ActiveRecord::VERSION::MINOR >= 1))
455
- <<-sql
456
- SELECT "tasks".* FROM "tasks" INNER JOIN "projects" ON "projects"."id" = "tasks"."project_id" AND "projects"."account_id" = 1 LEFT JOIN project_categories pc ON project.category_id = pc.id WHERE "tasks"."account_id" = 1
457
- sql
458
- else
459
- <<-sql
460
- SELECT "tasks".* FROM "tasks" INNER JOIN "projects" ON "projects"."account_id" = 1 AND "projects"."id" = "tasks"."project_id" LEFT JOIN project_categories pc ON project.category_id = pc.id WHERE "tasks"."account_id" = 1
461
- sql
462
- end
582
+ option1 = <<-sql.strip
583
+ SELECT "tasks".* FROM "tasks" INNER JOIN "projects" ON "projects"."id" = "tasks"."project_id" AND "projects"."account_id" = 1 LEFT JOIN project_categories pc ON project.category_id = pc.id WHERE "tasks"."account_id" = 1
584
+ sql
585
+ option2 = <<-sql.strip
586
+ SELECT "tasks".* FROM "tasks" INNER JOIN "projects" ON "projects"."account_id" = 1 AND "projects"."id" = "tasks"."project_id" LEFT JOIN project_categories pc ON project.category_id = pc.id WHERE "tasks"."account_id" = 1
587
+ sql
463
588
 
464
589
  project1 = Project.create! name: 'Project 1'
465
590
  projectcategory = ProjectCategory.create! name: 'project cat 1', project: project1, category: category1
466
591
 
467
592
  project1.tasks.create! name: 'baz'
468
- expect(Task.joins(:project).joins('LEFT JOIN project_categories pc ON project.category_id = pc.id').to_sql).to eq(expected_sql.strip)
593
+ expect(Task.joins(:project).joins('LEFT JOIN project_categories pc ON project.category_id = pc.id').to_sql).to eq(option1).or(eq(option2))
469
594
  end
470
595
 
471
596
  MultiTenant.without do
472
597
  expected_sql = <<-sql
473
- SELECT "tasks".* FROM "tasks" INNER JOIN "projects" ON "projects"."id" = "tasks"."project_id" AND "projects"."account_id" = "tasks"."account_id" LEFT JOIN project_categories pc ON project.category_id = pc.id WHERE "tasks"."account_id" = 1
598
+ SELECT "tasks".* FROM "tasks" INNER JOIN "projects" ON "projects"."id" = "tasks"."project_id" AND "tasks"."account_id" = "projects"."account_id" LEFT JOIN project_categories pc ON project.category_id = pc.id WHERE "tasks"."account_id" = 1
474
599
  sql
475
600
 
476
601
  expect(Task.where(account_id: 1).joins(:project).joins('LEFT JOIN project_categories pc ON project.category_id = pc.id').to_sql).to eq(expected_sql.strip)
@@ -485,32 +610,39 @@ describe MultiTenant do
485
610
  project2 = Project.create! name: 'Project 2', account: Account.create!(name: 'Account2')
486
611
 
487
612
  MultiTenant.with(account) do
488
- expected_sql = if uses_prepared_statements? && ActiveRecord::VERSION::MAJOR > 5
489
- <<-sql.strip
490
- SELECT "projects".* FROM "projects" WHERE "projects"."account_id" = #{account.id} AND "projects"."id" = $1 LIMIT $2
491
- sql
492
- else
493
- <<-sql.strip
494
- SELECT "projects".* FROM "projects" WHERE "projects"."account_id" = #{account.id} AND "projects"."id" = $1 LIMIT $2
495
- sql
496
- end
497
-
498
- expect(Project).to receive(:find_by_sql).with(expected_sql, any_args).and_call_original
613
+ option1 = <<-sql.strip
614
+ SELECT "projects".* FROM "projects" WHERE "projects"."account_id" = #{account.id} AND "projects"."id" = $1 LIMIT $2
615
+ sql
616
+ option2 = <<-sql.strip
617
+ SELECT "projects".* FROM "projects" WHERE "projects"."id" = $1 AND "projects"."account_id" = #{account.id} LIMIT $2
618
+ sql
619
+ option3 = <<-sql.strip
620
+ SELECT "projects".* FROM "projects" WHERE "projects"."id" = $1 AND "projects"."account_id" = #{account.id} LIMIT $2
621
+ sql
622
+
623
+ # Couldn't make the following line pass for some reason, so came up with an uglier alternative
624
+ # expect(Project).to receive(:find_by_sql).with(eq(option1).or(eq(option2)).or(eq(option3)), any_args).and_call_original
625
+ expect(Project).to receive(:find_by_sql).and_wrap_original do |m, *args|
626
+ expect(args[0]).to(eq(option1).or(eq(option2)).or(eq(option3)))
627
+ m.call(args[0], args[1], preparable:args[2][:preparable])
628
+ end
499
629
  expect(Project.find(project.id)).to eq(project)
500
630
  end
501
631
 
502
632
  MultiTenant.without do
503
- expected_sql = if uses_prepared_statements? && ActiveRecord::VERSION::MAJOR > 5
504
- <<-sql.strip
505
- SELECT "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT $2
506
- sql
507
- else
508
- <<-sql.strip
509
- SELECT "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT $2
510
- sql
511
- end
512
-
513
- expect(Project).to receive(:find_by_sql).with(expected_sql, any_args).and_call_original
633
+ option1 = <<-sql.strip
634
+ SELECT "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT $2
635
+ sql
636
+ option2 = <<-sql.strip
637
+ SELECT "projects".* FROM "projects" WHERE "projects"."id" = $1 LIMIT $2
638
+ sql
639
+
640
+ # Couldn't make the following line pass for some reason, so came up with an uglier alternative
641
+ # expect(Project).to receive(:find_by_sql).with(eq(option1).or(eq(option2)), any_args).and_call_original
642
+ expect(Project).to receive(:find_by_sql).and_wrap_original do |m, *args|
643
+ expect(args[0]).to(eq(option1).or(eq(option2)))
644
+ m.call(args[0], args[1], preparable:args[2][:preparable])
645
+ end
514
646
  expect(Project.find(project2.id)).to eq(project2)
515
647
  end
516
648
  end
@@ -98,4 +98,21 @@ describe "Query Rewriter" do
98
98
  }.not_to raise_error
99
99
  end
100
100
  end
101
+
102
+ context "when joining with a model with a default scope" do
103
+ let!(:account) { Account.create!(name: "Test Account") }
104
+
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)
110
+
111
+ expect(
112
+ MultiTenant.with(account) do
113
+ Page.joins(:domain).pluck(:id)
114
+ end
115
+ ).to eq([page_in_alive_domain.id])
116
+ end
117
+ end
101
118
  end
@@ -54,6 +54,25 @@ describe MultiTenant, 'Record modifications' do
54
54
  end
55
55
  end
56
56
 
57
+ it 'should not update other objects with same id when calling object.update_columns' do
58
+ # When two records with same id but different account_id are updated, it should only update the current one
59
+ expect(project.account).to eq(account)
60
+ expect(project2.account).to eq(account2)
61
+ expect(project.id).to eq(project2.id)
62
+
63
+ MultiTenant.without do
64
+ project2.update_columns(name: 'newthing2')
65
+ expect(project.reload.name).to eq('something')
66
+ expect(project2.reload.name).to eq('newthing2')
67
+ end
68
+ end
69
+
70
+ it 'should return the same object when calling object.reload' do
71
+ # When two records with same id but different account_id are updated, it should not return the other object
72
+ expect(project.reload.account_id).to eq(account.id)
73
+ expect(project2.reload.account_id).to eq(account2.id)
74
+ end
75
+
57
76
  it 'test delete for reference tables' do
58
77
  category1 = Category.create! name: 'Category 1'
59
78
  expect(Category.count).to eq(1)
@@ -5,6 +5,9 @@ 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') }
9
+
10
+ before { deleted_acount.destroy! }
8
11
 
9
12
  describe 'server middleware' do
10
13
  it 'sets the multitenant context when provided in message' do
@@ -15,6 +18,14 @@ describe MultiTenant, 'Sidekiq' do
15
18
  end
16
19
  end
17
20
 
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)
26
+ end
27
+ end
28
+
18
29
  it 'does not set the multitenant context when no tenant provided' do
19
30
  server.call(double, {'bogus' => 'message'}, 'bogus_queue') do
20
31
  expect(MultiTenant.current_tenant).to be_nil
data/spec/schema.rb CHANGED
@@ -34,6 +34,13 @@ ARGV.grep(/\w+_spec\.rb/).empty? && ActiveRecord::Schema.define(version: 1) do
34
34
  t.column :type, :string
35
35
  end
36
36
 
37
+ create_table :optional_sub_tasks, force: true do |t|
38
+ t.references :account, :integer
39
+ t.column :sub_task_id, :integer
40
+ t.column :name, :string
41
+ t.column :type, :string
42
+ end
43
+
37
44
  create_table :countries, force: true do |t|
38
45
  t.column :name, :string
39
46
  end
@@ -89,10 +96,26 @@ ARGV.grep(/\w+_spec\.rb/).empty? && ActiveRecord::Schema.define(version: 1) do
89
96
  t.column :category_id, :integer
90
97
  end
91
98
 
92
-
93
99
  create_table :allowed_places, force: true, id: false do |t|
94
- t.string :account_id, :integer
95
- t.string :name, :string
100
+ t.string :account_id, :integer
101
+ t.string :name, :string
102
+ end
103
+
104
+ create_table :domains, force: true, partition_key: :account_id do |t|
105
+ t.column :account_id, :integer
106
+ t.column :name, :string
107
+ t.column :deleted, :boolean, default: false
108
+ end
109
+
110
+ create_table :pages, force: true, partition_key: :account_id do |t|
111
+ t.column :account_id, :integer
112
+ t.column :name, :string
113
+ t.column :domain_id, :integer
114
+ end
115
+
116
+ create_table :posts, force: true, partition_key: :account_id do |t|
117
+ t.column :account_id, :integer
118
+ t.column :name, :string
96
119
  end
97
120
 
98
121
  create_distributed_table :accounts, :id
@@ -108,6 +131,9 @@ ARGV.grep(/\w+_spec\.rb/).empty? && ActiveRecord::Schema.define(version: 1) do
108
131
  create_distributed_table :uuid_records, :organization_id
109
132
  create_distributed_table :project_categories, :account_id
110
133
  create_distributed_table :allowed_places, :account_id
134
+ create_distributed_table :domains, :account_id
135
+ create_distributed_table :pages, :account_id
136
+ create_distributed_table :posts, :account_id
111
137
  create_reference_table :categories
112
138
  end
113
139
 
@@ -115,6 +141,7 @@ class Account < ActiveRecord::Base
115
141
  multi_tenant :account
116
142
  has_many :projects
117
143
  has_one :manager, inverse_of: :account
144
+ has_many :optional_sub_tasks
118
145
  end
119
146
 
120
147
  class Project < ActiveRecord::Base
@@ -146,6 +173,14 @@ class SubTask < ActiveRecord::Base
146
173
  multi_tenant :account
147
174
  belongs_to :task
148
175
  has_one :project, through: :task
176
+ has_many :optional_sub_tasks
177
+ end
178
+
179
+ with_belongs_to_required_by_default do
180
+ class OptionalSubTask < ActiveRecord::Base
181
+ multi_tenant :account, optional: true
182
+ belongs_to :sub_task
183
+ end
149
184
  end
150
185
 
151
186
  class StiSubTask < SubTask
@@ -185,6 +220,7 @@ class Comment < ActiveRecord::Base
185
220
  end
186
221
 
187
222
  class Organization < ActiveRecord::Base
223
+ multi_tenant :organization
188
224
  has_many :uuid_records
189
225
  end
190
226
 
@@ -204,7 +240,17 @@ class ProjectCategory < ActiveRecord::Base
204
240
  belongs_to :account
205
241
  end
206
242
 
207
-
208
243
  class AllowedPlace < ActiveRecord::Base
209
244
  multi_tenant :account
210
245
  end
246
+
247
+ class Domain < ActiveRecord::Base
248
+ multi_tenant :account
249
+ has_many :pages
250
+ default_scope { where(deleted: false) }
251
+ end
252
+
253
+ class Page < ActiveRecord::Base
254
+ multi_tenant :account
255
+ belongs_to :domain
256
+ end
data/spec/spec_helper.rb CHANGED
@@ -46,4 +46,11 @@ def uses_prepared_statements?
46
46
  ActiveRecord::Base.connection.prepared_statements
47
47
  end
48
48
 
49
+ def with_belongs_to_required_by_default(&block)
50
+ default_value = ActiveRecord::Base.belongs_to_required_by_default
51
+ ActiveRecord::Base.belongs_to_required_by_default = true
52
+ yield
53
+ ensure
54
+ ActiveRecord::Base.belongs_to_required_by_default = default_value
55
+ end
49
56
  require 'schema'