activerecord-multi-tenant 1.1.1 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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'