Skip to content

Tenancy Reference

Three closely-coupled concerns:

  1. Entity scoping — every record belongs to a tenant; queries filter automatically.
  2. Nested resources — parent/child URLs; parent scoping takes precedence over entity scoping.
  3. Invites — onboarding users into a tenant's membership.

How entity scoping fits together

Three cooperating pieces:

PieceRole
PortalDeclares the entity class and how to resolve it from the request (scope_to_entity Organization, strategy: :path).
Policydefault_relation_scope(relation) calls relation.associated_with(entity_scope) on every collection query. Enforced via verify_default_relation_scope_applied!.
Modelassociated_with(entity) resolves via custom scope, direct association, or has_one :through.

Configure the portal once. The policy and model conventions then carry tenancy automatically.

🚨 Critical (applies to all three sub-pages)

  • Never bypass default_relation_scope. Overriding relation_scope with where(organization: ...) or manual joins triggers verify_default_relation_scope_applied!. Always call default_relation_scope(relation) explicitly — not super.
  • Always declare an association path from the model to the entity. Direct belongs_to, has_one :through, or a custom associated_with_<entity> scope. If associated_with can't resolve, fix the model, not the policy.
  • Parent scoping beats entity scoping. When a parent is present (nested resource), default_relation_scope scopes via the parent, not via entity_scope. Don't double-scope.
  • One level of nesting only. Grandparent → parent → child nested routes are NOT supported. Use top-level routes for deeper relationships.
  • Compound uniqueness scoped to the tenant FK. validates :code, uniqueness: {scope: :organization_id} — without this, uniqueness leaks across tenants.
  • Invite email must match the accepting user's email. Security feature — don't disable enforce_email? lightly.

Released under the MIT License.