How do I set a default_scope for a has_many :through association based on the join table?

528 views Asked by At

My question

How do I create a default_scope for Users such that it limits visibility to only those users who have a tenants_users record for the current tenant (tenants_users.tenant_id == Tenant.current_id) in the join table for the has_many :through association?? I think this is ultimately a syntax question, but it may be deeper than that.

What I'm doing

I'm implementing multitenancy using Multitenancy with Scopes (subscription required) as a guide. Ryan uses a default_scope to limit available rows based on the client's current tenant. He basically adds a tenant_id column to all tables that contain tenant-specific data, then includes a default_scope in each model:

default_scope { where(tenant_id: Tenant.current_id) }

[Tenant.current_id (a cattr_accessor) is set when a user logs in (Tenant.current_id = the tenant_id for the Tenant the user selects).]

This works great for me, except I want a many-to-many relationship for Users and Tenants (as opposed to many-user-to-one-tenant as the Railscast assumes): A Tenant can have multiple users, AND I want Users to have access to Multiple tenants. So, instead of a tenant_id column on my Users table, I have a tenants_users join table that has a tenant_id and user_id for each row.

A different description of the result I'm looking for

If I fire the up the Rails Console and set:

Tenant.current_id = 2

...then say...

User.all

...I want to see ONLY those users who have a row in the tenants_users table such that tenant_id == Tenant.current_id.

If I say:

User.unscoped.all

Then I want to see all the users, regardless of what Tenant.current_id is.

Code

tenant.rb

class Tenant < ActiveRecord::Base
  cattr_accessor :current_id
  has_many :users, :through => :tenants_users
  has_many :tenants_users

  def self.current_id=(id)
    Thread.current[:tenant_id] = id
  end

  def self.current_id
    Thread.current[:tenant_id]
  end
end

user.rb

class User < ActiveRecord::Base
  # This was the default_scope before I moved tenant_id to the tenants_users table
  # default_scope { where(tenant_id: Tenant.current_id) }

  # What should it be now?
  default_scope ???

  has_many :tenants_users
  has_many :tenants, :through => :tenants_users
end

tenants_user.rb

class TenantsUser < ActiveRecord::Base
  belongs_to :tenant
  belongs_to :user
end
1

There are 1 answers

1
Cristian On

This basically joins the tenants_users table and puts the condition on the tenant_id attribute:

default_scope { where(tenants_users: {tenant_id: Tenant.current_id}) }