You are on the right track using CanCan, or CanCanCan since CanCan is deprecated, I think.
I don't like the default_scope
for the reason that it is not threadsafe. The user id is stored in a class variable, which means that two or more concurrent users in your app will break this unless you use Unicorn or some other web server that makes sure no more than one single client connection will access the same thread.
You should therefore use something like Cancan.
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
# User's own Topics only:
can :manage, Topic, user_id: user.id
# or, with a Tenant
can :manage, Topic, tenant_id: user.tenant.id if user.tenant # User belongs_to Tenant
can :manage, Topic, tenant_id: user.tenants.map(&:id) if user.tenants.any? # User has_many Tenants
else
can :read, Topic # Anyone can read any topic.
end
end
end
Pick the strategy you need from the three examples above.
EDIT Slightly more complicated example for Multi-Tenant Admins for @JoshDoody's question in the comments:
class Admin < User; end
class TenantAdmin
belongs_to :tenant
belongs_to :admin, class_name: User
end
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.admin?
can :manage, Topic, tenant_id: TenantAdmin.where(admin: user).map(&:tenant_id)
else
can :read, Topic # Anyone can read any topic
end
end
end
Now, it might be that this is not as performant as you would like, but the general idea is that you have TenantAdmins who will be able to manage Topics within their Tenants.
Hope this helps.