diff --git a/CHANGELOG.md b/CHANGELOG.md index ae2bc7f..1ad5e33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Docs: the onboarding path now works when followed literally. Both + tutorials and the README quick start were re-run in fresh apps from + the published gem. The 10-minute tutorial claimed SQLite works (the + migrations use `jsonb` and fail), sent readers to a nonexistent + `/auth/sign_up`, and had them add mount lines the generators already + write. All onboarding docs now cover PostgreSQL, `bundle install` + after each generator, `bin/rails db:encryption:init` (without it, + sign-up raises `Missing Active Record encryption credential`), and + `bin/rails tailwindcss:build` after `design --shell`. The Engine + Catalogue's wiring example now matches what the generators write. + Docs links point at davidslv.uk/seams. + ## [0.2.0] — 2026-09-27 > [!WARNING] diff --git a/README.md b/README.md index e69e198..35b7d50 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Gem Version](https://img.shields.io/gem/v/seams.svg)](https://rubygems.org/gems/seams) [![CI](https://github.com/Davidslv/seams/actions/workflows/ci.yml/badge.svg)](https://github.com/Davidslv/seams/actions/workflows/ci.yml) -[![Docs site](https://img.shields.io/badge/docs-davidslv.github.io%2Fseams-blue.svg)](https://davidslv.github.io/seams/) +[![Docs site](https://img.shields.io/badge/docs-davidslv.uk%2Fseams-blue.svg)](https://davidslv.uk/seams/) [![API docs](https://img.shields.io/badge/api-rubydoc.info-blue.svg)](https://rubydoc.info/gems/seams) [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) @@ -13,7 +13,7 @@ You ship one Rails app. Inside it, each feature (auth, accounts, billing, teams, Every generated file is plain Rails code in your repo. You can read it, change it, or delete it. Nothing is hidden behind the gem. > [!NOTE] -> Seams is the executable companion to the book **[Modular Rails: Architecture for the Long Game](https://davidslv.uk/modular-rails/)**. The full guides live on the **[documentation site](https://davidslv.github.io/seams/)**. [seams-example](https://github.com/Davidslv/seams-example) is a reference host with every engine wired up. +> Seams is the executable companion to the book **[Modular Rails: Architecture for the Long Game](https://davidslv.uk/modular-rails/)**. The full guides live on the **[documentation site](https://davidslv.uk/seams/)**. [seams-example](https://github.com/Davidslv/seams-example) is a reference host with every engine wired up. ## Requirements @@ -43,28 +43,40 @@ Then install the framework: ```bash bundle install bin/rails generate seams:install +bundle install ``` -`seams:install` adds the framework files, a CI workflow, and a `bin/seams` command. Every step after this uses `bin/seams`. +`seams:install` adds the framework files, a CI workflow, a `bin/seams` command, and a few development gems (hence the second `bundle install`). Every step after this uses `bin/seams`. ## Quick start -Generate the engines you need. The order matters, because later engines build on earlier ones: +Generate the engines you need. The order matters, because later engines build on earlier ones. Each generator can add gems to your Gemfile, so run `bundle install` after each one, before the next: ```bash -bin/seams core # shared building blocks (always first) -bin/seams auth # sign-in, sessions, OAuth, API tokens -bin/seams accounts # the tenant (Account) and its members -bin/seams notifications # in-app, email, and SMS notifications -bin/seams billing # Stripe subscriptions -bin/seams teams # optional teams inside an account -bin/seams design --shell # UI components and an app layout +bin/seams core && bundle install # shared building blocks (always first) +bin/seams auth && bundle install # sign-in, sessions, OAuth, API tokens +bin/seams accounts && bundle install # the tenant (Account) and its members +bin/seams notifications && bundle install # in-app, email, and SMS notifications +bin/seams billing && bundle install # Stripe subscriptions +bin/seams teams && bundle install # optional teams inside an account +bin/seams design --shell && bundle install # UI components and an app layout +``` -bundle install +Then finish the setup: + +```bash +bin/rails tailwindcss:build # the design layout loads the compiled CSS +bin/rails db:encryption:init # auth encrypts personal data; paste the printed +bin/rails credentials:edit # active_record_encryption block into credentials bin/rails db:migrate -bin/seams list # show engines, their events, and subscribers +bin/seams list # show engines, their events, and subscribers ``` +> [!IMPORTANT] +> Skip the encryption keys and sign-up fails with `Missing Active Record encryption credential`. Skip the Tailwind build and every page using the design layout fails with `The asset 'tailwind.css' was not found`. + +Start the app with `bin/rails server`. Sign up at `/auth/registration/new` and sign in at `/auth/session/new`. + > [!TIP] > New to Seams? Follow **[Getting Started](doc/tutorials/GETTING_STARTED.md)**. It goes step by step from `bundle install` to a running app. @@ -168,7 +180,7 @@ Seams writes code into your app. So updating the gem does not change engines you ## Documentation -The **[documentation site](https://davidslv.github.io/seams/)** has every guide below, with search. The API reference is on **[rubydoc.info](https://rubydoc.info/gems/seams)**. +The **[documentation site](https://davidslv.uk/seams/)** has every guide below, with search. The API reference is on **[rubydoc.info](https://rubydoc.info/gems/seams)**.
Start here diff --git a/doc/reference/ENGINE_CATALOGUE.md b/doc/reference/ENGINE_CATALOGUE.md index 86c437b..6beefc4 100644 --- a/doc/reference/ENGINE_CATALOGUE.md +++ b/doc/reference/ENGINE_CATALOGUE.md @@ -248,28 +248,39 @@ the example `_quire.css` theme. See ## Pulling them together -A real B2B SaaS using all six: +A B2B SaaS using the canonical engines. The generators write the +routes for you. After `core`, `auth`, `accounts`, `notifications`, +`billing`, `teams` and `admin`, `config/routes.rb` contains: ```ruby -# config/routes.rb -mount Core::Engine, at: "/" # no public routes — mount for completeness -mount Auth::Engine, at: "/auth" -mount Accounts::Engine, at: "/accounts" -mount Notifications::Engine, at: "/notifications" -mount Billing::Engine, at: "/billing" +# config/routes.rb (written by the generators) +mount Seams::Admin::Engine, at: "/admin" mount Teams::Engine, at: "/teams" +mount Billing::Engine, at: "/billing" +mount Notifications::Engine, at: "/notifications" +mount Accounts::Engine, at: "/accounts" +mount Auth::Engine, at: "/auth" +mount Core::Engine, at: "/core" ``` +The auth generator also adds `include Auth::Authentication` to your +`ApplicationController`. Add the other two concerns yourself, so the +per-request `Current` attributes cascade into core and accounts: + ```ruby # app/controllers/application_controller.rb class ApplicationController < ActionController::Base - include Auth::Authentication # 1. sets Auth::Current.identity - include Core::HasCurrentAttributes # 2. reads Auth::Current.identity (depends on 1) - include Accounts::Authorization # 3. reads Auth::Current.identity (depends on 1) - before_action :authenticate_identity! + include Auth::Authentication # 1. added by `bin/seams auth`; sets Auth::Current.identity + include Core::HasCurrentAttributes # 2. add this; reads Auth::Current.identity (depends on 1) + include Accounts::Authorization # 3. add this; reads Auth::Current.identity (depends on 1) + before_action :authenticate_identity! # optional: require sign-in app-wide end ``` +With the app-wide `before_action`, host pages that must stay public +need `skip_before_action :authenticate_identity!`. The auth engine's +own sign-in, sign-up and password-reset actions already skip it. + Order matters: `Auth::Authentication` must be included BEFORE `Core::HasCurrentAttributes` and `Accounts::Authorization` so `Auth::Current.identity` is bound before the other engines read it. @@ -290,5 +301,5 @@ class User < ApplicationRecord end ``` -That's a Bullet-Train-class app surface in six `bin/seams` +That's a Bullet-Train-class app surface in a handful of `bin/seams` commands. The code is yours; nothing is hidden behind a gem. diff --git a/doc/tutorials/GETTING_STARTED.md b/doc/tutorials/GETTING_STARTED.md index 1a016f3..49f19bd 100644 --- a/doc/tutorials/GETTING_STARTED.md +++ b/doc/tutorials/GETTING_STARTED.md @@ -7,18 +7,21 @@ generating your first canonical engine. - Ruby 3.3+ - Rails 7.1+ (8.x recommended) +- PostgreSQL. The engines use `jsonb` columns, so SQLite and MySQL are + not supported. For a new app: `rails new myapp --database=postgresql`. - A new or existing Rails application ## 1. Install ```ruby # Gemfile -gem "seams" +gem "seams", "~> 0.2" ``` ```bash bundle install bin/rails generate seams:install +bundle install # install adds gems (rspec-rails, rubocop, brakeman, ...) ``` The install generator scaffolds: @@ -32,56 +35,57 @@ The install generator scaffolds: - `script/run_affected_tests.sh`, `script/collate_coverage.rb` — host-local helpers - `doc/ARCHITECTURE.md` — per-host architecture template -## 2. Generate your first engine +## 2. Generate your first engines + +`core` comes first. Every other engine builds on it. ```bash +bin/seams core bin/seams auth ``` -Look at what it created: +Look at what `auth` created: ```bash -$ tree engines/auth -L 2 -engines/auth -├── LICENSE -├── README.md -├── auth.gemspec -├── app/ -│ ├── controllers/ -│ ├── models/ -│ └── views/ -├── config/ -│ └── routes.rb -├── db/ -│ └── migrate/ -├── lib/ -└── spec/ +$ ls engines/auth +app auth.gemspec config db Gemfile lib LICENSE Rakefile README.md spec +$ ls engines/auth/app +controllers jobs mailers models services views ``` -## 3. Wire it up +## 3. What the generators wire up, and what you do -Add the engine's mount line to your host routes: +The generators edit your app for you: -```ruby -# config/routes.rb -Rails.application.routes.draw do - mount Auth::Engine, at: "/auth" -end -``` +- a `mount` line per engine in `config/routes.rb` (`/auth`, `/core`, ...) +- `include Auth::Authentication` in your `ApplicationController` +- `config/initializers/.rb` for each engine +- any gems the engine needs, in your Gemfile -Add the authentication concern to your ApplicationController: +You do three things: -```ruby -class ApplicationController < ActionController::Base - include Auth::Authentication -end -``` +1. **Install the gems they added:** -Run migrations: + ```bash + bundle install + ``` -```bash -bin/rails db:migrate -``` +2. **Create encryption keys.** The auth engine encrypts personal data + at rest with Active Record encryption. Without keys, sign-up fails + with `Missing Active Record encryption credential`. + + ```bash + bin/rails db:encryption:init # prints an active_record_encryption: block + bin/rails credentials:edit # paste the block in and save + ``` + +3. **Run the migrations:** + + ```bash + bin/rails db:migrate + ``` + +Sign-up is at `/auth/registration/new` and sign-in at `/auth/session/new`. ## 4. Generate more engines @@ -102,11 +106,17 @@ Every time you generate a new engine the existing engines' `.rubocop.yml` files are auto-updated so the boundary cops cover the new engine without manual edits. +Run `bundle install` and `bin/rails db:migrate` after generating. Several +engines add gems: billing adds `stripe`, admin adds `administrate` and +`pundit`, design adds `tailwindcss-rails`. After `bin/seams design`, also +run `bin/rails tailwindcss:build`, because the layout loads the compiled CSS. + The recommended order is `core → auth → accounts → notifications → billing → teams`. Some engines depend on each other (accounts on auth, billing on accounts) — see each engine's README for the "Requires:" line. `permissions`, `admin`, and `design` are opt-in and -can be added at any time. Run `bin/seams design --shell` to also get a +can be added at any time. Only staff can open the admin area: set +`staff: true` on an `Auth::Identity` to let someone in. Run `bin/seams design --shell` to also get a ready-to-boot application layout and signed-in dashboard, which makes the other engines visible in a real, styled UI. diff --git a/doc/tutorials/TUTORIAL.md b/doc/tutorials/TUTORIAL.md index b5ed9fe..4018afe 100644 --- a/doc/tutorials/TUTORIAL.md +++ b/doc/tutorials/TUTORIAL.md @@ -1,20 +1,22 @@ # Tutorial: your first engine in 10 minutes -By the end of this you'll have a fresh Rails app with a Seams auth engine -and a styled, signed-in dashboard — booting locally. Follow every step in -order; each builds on the last. +By the end of this you'll have a new Rails app with a Seams auth engine +and a styled dashboard you can sign up into, running locally. Follow +every step in order. Each one builds on the last. -> This is a **tutorial** (learning by doing). For the per-generator -> reference, see [GETTING_STARTED.md](GETTING_STARTED.md) and -> [ENGINE_CATALOGUE.md](../reference/ENGINE_CATALOGUE.md). +> This is a **tutorial** (learning by doing). For what each generator +> writes, see [GETTING_STARTED.md](GETTING_STARTED.md) and the +> [Engine Catalogue](../reference/ENGINE_CATALOGUE.md). -**Prerequisites:** Ruby 3.3+, Rails 7.1+ (8.x recommended), and a -database (the default SQLite is fine for this). +**Prerequisites:** Ruby 3.3+, Rails 7.1+ (8.x recommended), and +**PostgreSQL** running locally. Seams engines use `jsonb` columns, so +the default SQLite database will not work. ## 1. A new Rails app (≈2 min) ```bash -rails new blog && cd blog +rails new blog --database=postgresql && cd blog +bin/rails db:create ``` ## 2. Add Seams (≈1 min) @@ -22,69 +24,78 @@ rails new blog && cd blog ```bash bundle add seams bin/rails generate seams:install +bundle install ``` -`seams:install` wires the framework into your host: a `bin/seams` -wrapper, the engine load path, a CI workflow, and an opinionated quality -toolchain (strong_migrations + lefthook by default; see -[`--no-*` flags](GETTING_STARTED.md)). +`seams:install` wires the framework into your app: a `bin/seams` +command, the engine load path, a CI workflow, and a quality toolchain +(strong_migrations and lefthook by default). It also adds gems to your +Gemfile, which is why `bundle install` follows it. -## 3. Generate the shared core + auth (≈2 min) +## 3. Generate the shared core and auth (≈2 min) ```bash bin/seams core bin/seams auth ``` -- `core` ships the primitives every engine builds on (Current attributes, - the audit log, shared concerns). -- `auth` is a real Rails engine under `engines/auth/` — Identity, Session, - sign-up/in/out flows, and the `Auth::Authentication` controller concern. - The code is yours to read and edit. +- `core` has the pieces every engine builds on: `Current` attributes, + the audit log, shared concerns. +- `auth` is a real Rails engine under `engines/auth/`: `Identity`, + `Session`, sign-up, sign-in and sign-out, and the + `Auth::Authentication` controller concern. The code is yours to read + and edit. + +The generators wire each engine into your app for you. They add the +`mount` lines to `config/routes.rb` and include `Auth::Authentication` +in your `ApplicationController`. ## 4. Add a styled shell (≈1 min) ```bash bin/seams design --shell +bundle install +bin/rails tailwindcss:build ``` -`design --shell` generates the design system **and** an application -layout plus a signed-in starter dashboard, so the app looks like a -product rather than Rails scaffolding. +`design --shell` generates the design system, an application layout, +and a starter dashboard at `/` and `/dashboard`. The layout loads the +compiled Tailwind CSS, so build it once now. When you change styles +later, rebuild, or run `bin/rails tailwindcss:watch` in a second +terminal. -## 5. Wire it together (≈2 min) +## 5. Set up encryption keys and the database (≈2 min) -Mount the engines in `config/routes.rb`: +The auth engine encrypts personal data such as email addresses at rest, +using Active Record encryption. Generate keys and store them in your +credentials: -```ruby -Rails.application.routes.draw do - mount Auth::Engine, at: "/auth" -end +```bash +bin/rails db:encryption:init +bin/rails credentials:edit ``` -Mix the authentication concern into your `ApplicationController`: +`db:encryption:init` prints an `active_record_encryption:` block. Paste +it into the credentials file and save. -```ruby -class ApplicationController < ActionController::Base - include Auth::Authentication -end -``` +> Without these keys, signing up fails with +> `Missing Active Record encryption credential`. -Create the tables: +Then create the tables: ```bash bin/rails db:migrate ``` -## 6. Boot it (≈1 min) +## 6. Run it (≈1 min) ```bash bin/rails server ``` -Visit `http://localhost:3000/auth/sign_up`, create an account, and you -land on the styled dashboard at `/dashboard`. That's auth + a product UI -in six commands — every file in your repo, nothing hidden behind the gem. +Visit `http://localhost:3000/auth/registration/new` and create an +account. You land on the styled dashboard. Sign in again later at +`/auth/session/new`. ## 7. See what you have @@ -92,12 +103,15 @@ in six commands — every file in your repo, nothing hidden behind the gem. bin/seams list ``` -Lists each engine and the events it emits and subscribes to. +This lists each engine, the events it publishes, and what it subscribes to. ## Where to go next -- Add more product surface: `bin/seams accounts`, `bin/seams billing`, - `bin/seams teams` — see the [Engine Catalogue](../reference/ENGINE_CATALOGUE.md). -- Make a file your own (stop the generator overwriting it): - `bin/seams resolve --eject auth/`. -- Understand the boundaries: [ARCHITECTURE.md](../explanation/ARCHITECTURE.md). +- **Add more features:** `bin/seams accounts`, `bin/seams billing` and + `bin/seams teams`, in that order. Run `bundle install` and + `bin/rails db:migrate` after each. See the + [Engine Catalogue](../reference/ENGINE_CATALOGUE.md). +- **Keep your changes to a generated file:** run + `bin/seams resolve --eject auth/`, and the generator will leave + that file alone. +- **Understand the boundaries:** [ARCHITECTURE.md](../explanation/ARCHITECTURE.md).