Roles and Teams
Member and admin roles today, and the documented path to multi-user teams later.
One Shot ships two roles today, member and admin, and one user per account. That's a narrower
model than most SaaS products eventually want, and it's narrow on purpose: it's the smallest thing
that's correct, with a documented path to grow past it without a rewrite.
What exists today
User#role is either member or admin. There's no admin UI: an admin operates on data through
bin/rails console, working through an account the same way any other code does
(account = Account.find(...)). The main thing admin? does today is exempt an account from
entitlement checks, so an admin can reach a paid controller without a subscription; see
Authorization. You can extend the same flag to gate an admin
area of your own if you build one.
Because there's exactly one user per account, "who's on this team" isn't a question the app has to
answer yet. The account is the user, for authorization purposes, and every policy, every scoped
query, and every billing check already keys off Current.account rather than Current.user. That's
the distinction that makes the next section additive rather than disruptive.
Adding real multi-user teams
When you need more than one person sharing a workspace, the model doesn't need to move much, because everything already scopes to the account rather than the user:
- Add a join model with a role, something like
Membership, joiningaccount,user, and arolecolumn (owner,member, whatever your product needs). LetUserbelong to manyAccounts through it. - Add
owner_idtoAccount, and set it to today's implicit single user so existing accounts keep working with no migration of behavior, only of data. - Add an account switcher in the signed-in UI that sets
Current.accountto whichever workspace the user picked. This is the one new piece of session state; everything downstream already readsCurrent.account. - Build the invite flow: an invite record, an email, and an acceptance path that creates the
Membership. This is genuinely new work, unlike the first three steps.
What you will not need to do: touch every model to add a new scoping column, rewrite policies
that already filter through ApplicationPolicy::Scope, or change how billing attaches (it's
already on Account, not User, specifically so a shared subscription needs no rework). The
tenancy boundary was the expensive decision, and it's already made; see
Accounts and Tenancy for why it was made this way from
the start.
Next
With the domain model clear, see how to add a feature on top of it: Adding a Resource.