Docs · Building Features

Adding a Resource

The crud-with-billing shape: model, policy, controller, views, and specs for a new account-scoped resource.

Almost every feature you add to a One Shot app is the same shape: a model, a policy, a controller, a set of views, routes, and specs, all scoped to the current account. The kit ships one working example of this shape, the Project resource, specifically so you can copy it rather than design it from scratch each time. The fastest way to add one is the crud-with-billing skill in Claude Code, which generates exactly this shape for you.

/crud-with-billing

Tell it the resource name (for example Invoice), its attributes (amount:integer note:text), and whether it's a paid feature or a free one.

The shape, piece by piece

  1. Model and migration. bin/rails g model Invoice account:references amount:integer note:text, then add null: false where it belongs and any validations. Every model gets belongs_to :account; see Accounts and Tenancy for why that's non-negotiable.
  2. The account association. has_many :invoices, dependent: :destroy on app/models/account.rb, so Current.account.invoices works everywhere else.
  3. A Pundit policy. app/policies/invoice_policy.rb, subclassing ApplicationPolicy. Its inherited Scope already filters to the acting user's account; you don't write that part yourself. See Authorization.
  4. A controller. Query only through Current.account.invoices, never Invoice.all. Call authorize and policy_scope. If this is a paid feature, include RequireEntitlement; if it's free, leave it out. See Gating a Feature.
  5. Views. index, show, new, edit, and _form, using the Terminal design system classes already defined in app/assets/stylesheets/application.css (.card, .btn, .field, .eyebrow). Copy the Project views as a starting layout.
  6. Routes. resources :invoices in config/routes.rb.
  7. Billing, only if this introduces a new plan or tier. Extend Billing::PLANS in app/adapters/billing.rb and add the price id to Billing::Stripe::PRICE_ENV; manage the actual Stripe product and price through the Stripe MCP server or bin/rails console.
  8. Specs. A model spec, a policy spec, and a request spec, always including the tenant-isolation test: fetching a record that belongs to a different account by id must 404, not 500 and not silently succeed. A paid resource also gets an entitlement-gate test: an unsubscribed account should be redirected to pricing, not shown the feature.

Verify

Run bin/check. The tenant-isolation test is the one that matters most here: if it's missing or passing for the wrong reason, a new resource can quietly leak data across accounts the moment it ships.

Next

If the resource needs work outside the request cycle, see Background Jobs.