teishoku

Rails 8 · Ruby 3.4 · MIT

Rails with the boundaries already drawn.

A teishoku is a set meal: everything you need, on one tray, nothing to assemble. This is that for a Rails 8 app. Bounded contexts, Service and Query objects, auth, admin, API, three languages, and a suite that fails the build when you cross a line.

Use this template
or clone it
git clone https://github.com/gxolivei/ruby-on-rails-8-ddd-template my-app
cd my-app && bin/setup_wizard

What's on the tray

Everything below is wired, tested, and enforced. Run the wizard and choose what to keep.

Bounded contexts

Every file lives in a context. Identity::User, not User. Crossing a boundary is written in the code, not an accident of autoloading.

Services and Queries

Writes return a Result. Reads return records. Controllers call them and never touch Active Record.

Generators that know the rules

ddd:context, ddd:model, ddd:query, ddd:service, ddd:controller --with query service. The plain Rails ones are shadowed and point you back.

Auth, done

Sign-in, sign-up, lockout, password policy, reset emails, GitHub OAuth.

Admin and API

A protected backoffice with branding, and a token-authenticated versioned JSON API.

Three languages

English, Português, Español across the whole UI. A literal string in a view fails the build.

Themes without JavaScript

Light, dark and system, driven by the user's preference through semantic tokens.

The Solid stack

Cache, queue and cable on PostgreSQL. No Redis.

The rules are tests

A .find in a controller fails the build. A .where in a view fails the build. A model outside a context fails the build.

The failure names the file and line, the generator to run, and the section of ARCHITECTURE.md to read.

bin/rspec spec/architecture
app/controllers/billing/invoices_controller.rb:7 — controller calls User.find(params[:id]) directly; move the read into a Query or the write into a Service
    Fix: bin/rails g ddd:query Billing <Name>  (read)  or  bin/rails g ddd:service Billing <Name>  (write), then call Billing::<Name>Query.call / <Name>Service.call from here
    See: ARCHITECTURE.md §3 Strict Layer Contracts, §4 Service Object Pattern, §5 Query Object Pattern

Start in five commands

Every generator ends by telling you what to run next.

bin/setup_wizard, last screen
bin/rails g ddd:context Billing
    folders for the Billing context and its routes namespace
bin/rails g ddd:model Billing Invoice amount:decimal status:integer
    Billing::Invoice model, migration, factory and spec
bin/rails g ddd:query Billing ListInvoices
    Billing::ListInvoicesQuery, a read for a page, with spec
bin/rails g ddd:service Billing CreateInvoice
    Billing::CreateInvoiceService, a write that returns a Result, with spec
bin/rails g ddd:controller Billing Invoices index show create --with query service
    Billing::InvoicesController, views, routes, locale keys and request spec,
    plus the queries and services those actions call

100.0% line coverage, enforced.

The suite fails below it. SimpleCov's minimum_coverage gate is the mechanism, and the wizard runs the suite before it says you are done.