Snippets · v0.7.3
public-show-by-slug
GET /read/notes/{slug}: a public page per record, by slug (a path param), slugs unique and stable
- Commands
cargo offrails snippet show public-show-by-slug # read it: the README, every file, every wire cargo offrails snippet add public-show-by-slug # copy and wire it (--as <model> renames note) cargo offrails snippet remove public-show-by-slug # take it back- Files
migration/src/m20261018_000200_add_slug_to_notes.rssrc/controllers/note_pages.rssrc/models/note_slugs.rssrc/routes/note_pages.rssrc/views/note_pages.rstemplates/note_pages/show.htmltests/app/note_pages_test.rs
- Wires into
migration/src/lib.rsat samples: migration modsmigration/src/lib.rsat samples: migrationssrc/models/note/mod.rsat samples: columnssrc/models/mod.rsat samples: model modssrc/controllers/mod.rsat samples: controller modssrc/views/mod.rsat samples: view modssrc/routes.rsat samples: route modssrc/routes.rsat samples: routessrc/routes.rsat samples: pathssrc/routes.rsat samples: route tablestests/app/main.rsat samples: test mods
.offrails/template/snippets/public-show-by-slug/README.md
Snippet: public-show-by-slug
TL;DR
GET /read/notes/{slug}: a public page for one record, found by a slug in the path (/read/notes/hello-world), next to the resource's own/notes/{id}pages.- A route with a path parameter, end to end: the routes file (
/read/notes/{slug},paths::note_page(slug)), the handler (Path(slug): Path<String>), the query, the page, tests (found, 404, a taken slug gets-2, markup escaped).- Builds on the
resourcesnippet (notes) and theslugsnippet; adds a nullable, uniquenotes.slugcolumn. Add all three with the same--as(--as article→/read/articles/{slug}).- Not for a generated, owner-scoped resource (
g resource posts …in an app with accounts):snippet addrefuses without the resource snippet, and its code calls the public notes'note::findandNoteView, which a scoped model keeps private (find_for(db, &user, id)). There, write it from this one as the worked example:cargo offrails g controller blog show:/blog/{slug} --public, aslugcolumn (theslugsnippet'sunique), and a public query that says what is public (published_at <= now) marked// unscoped:at its call site.
When to use it
- Public pages with readable URLs: a blog post, a product, a profile, while the CRUD stays where it is (and can move behind sign-in).
- Not for: private records (a slug is guessable; scope the lookup to the user and use the generated
/notes/{id}), or pages without a row (cargo offrails g controller blog show --path /blog/{slug}writes a page with a path param and no query).
Files
| Path | What it is |
|---|---|
migration/src/m20261018_000200_add_slug_to_notes.rs | notes.slug, nullable, with the unique index notes_slug_key |
src/models/note_slugs.rs | assign(db, note) (slug from the title, unique, never changed once set), find_public(db, slug) |
src/controllers/note_pages.rs | GET /read/notes/{slug} |
src/views/note_pages.rs, templates/note_pages/show.html | the page (the resource's NoteView) |
src/routes/note_pages.rs | the route, paths::note_page(slug), its ROUTES row (public) |
tests/app/note_pages_test.rs | found by slug, 404, same-title-2, a slug never moves, markup as text |
Wiring
One line each (two for the column), marked public-show-by-slug:
migration/src/lib.rsatsamples: migration modsandsamples: migrationssrc/models/note/mod.rsatsamples: columns:#[sea_orm(unique)] pub slug: Option<String>,src/models/mod.rsatsamples: model mods;src/controllers/mod.rs,src/views/mod.rsat theirmodsanchorssrc/routes.rsatsamples: route mods,samples: routes,samples: paths,samples: route tablestests/app/main.rsatsamples: test mods
After adding
cargo run -q -- db migrate(thendb rollback && db migrate).- Give new notes their slug: in
src/controllers/notes/new.rs, afternote::create,let record = note_slugs::assign(&s.db, record).await?;. Existing rows: a one-off task that callsassignon each. - Link to the page:
{{ paths::note_page(slug) }}where a note has one (NoteViewgets aslugfield). - The sitemap: public slugs belong in it;
src/controllers/meta.rs'spublic_paths()is static, so add a query there (or a sitemap per resource).cargo offrails verify.
Adapting it
- Drafts: add
published_at(a migration) and filter on it infind_public; test that a draft is 404. - Slugs per owner (
/u/{user}/{slug}): a two-parameter path (Path((user, slug)): Path<(String, String)>), the unique index on(user_id, slug), andslug::uniquewith the owner in its query. - A slug that follows the title: call a variant of
assignon update that keeps the old slug as a redirect (anote_slug_redirectstable), or links break.
Remove it
cargo offrails snippet remove public-show-by-slug (roll the migration back first if it ran: cargo run -q -- db rollback), or delete the paths above and the lines marked public-show-by-slug.