Moneygun uses route-based multi-tenancy where resources are scoped under organizations:
/organizations/:organization_id/projects/:id
/organizations/:organization_id/memberships/:id
Why route-based? Unlike subdomain-based or user.organization_id approaches:
- Users can have multiple organizations open in different browser tabs
- No DNS/subdomain configuration required
- Clear resource ownership in URLs
The Current class provides request-scoped context throughout the application:
Current.organization # Current organization being accessed
Current.membership # Current user's membership in the organization
Current.user # Current authenticated user (via membership)
Current.organizations # All organizations the user belongs toAlways scope queries to the organization:
# Correct
Current.organization.projects
# Incorrect - exposes data across tenants
Project.allThe tenant. Has many users through memberships.
- Has subscriptions (via Pay gem)
- Has an owner (user)
- Includes concerns:
Billing,Multitenancy,Transfer,Logo,Community
Global user account authenticated via Devise.
- Can belong to multiple organizations
- Has connected accounts (OAuth via Google, GitHub)
Join table connecting User and Organization.
- Has role:
memberoradmin - Validates uniqueness of user + organization
- Cannot remove last admin or owner
Example organization-scoped resource demonstrating the pattern.
- Belongs to organization
- Belongs to membership (creator)
- Uses obfuscated IDs via Sqids (
ObfuscatesIdconcern)
Organization-scoped controllers inherit from Organizations::BaseController:
class Organizations::ProjectsController < Organizations::BaseController
def index
authorize Project
@projects = @organization.projects
end
endOrganizations::BaseController provides:
- Sets
@organizationfromcurrent_user.organizations - Sets
Current.membershipandCurrent.organization require_subscriptionfor paywalled features
Uses Pundit with membership-based policies:
# In ApplicationController
def pundit_user
Current.membership
endPolicies receive Current.membership as the user context, enabling role-based access control per organization.
Generate policies:
rails g pundit:policy resource_nameRoutes are split into separate files in config/routes/:
users.rb- User account routesorganizations.rb- Organization-scoped resourcesadmin.rb- Admin panel routes
Associate organization-scoped resources with membership (not user):
class Project < ApplicationRecord
belongs_to :organization
belongs_to :membership # Creator
endThis ties the resource to both the organization and the specific membership that created it.
Avo admin panel at /avo. Resources defined in app/avo/resources/.
- Tailwind CSS 4 with daisyUI 5 components
- Hotwire (Turbo + Stimulus) for interactivity
- ViewComponent for reusable UI components