Chapter 37: Capstone MVC App
Part VI: Capstone Applications
Why this chapter matters
The MVC capstone combines routes, controllers, views, data, migrations, seeds, and tests into a complete web application.
What you will build
You will build a database-backed project journal MVC app. The sample app has routes, a controller, an Active Record model, an escaped HTML view, static CSS, reversible SQL migrations, seeds, and MVC tests. The self-check commands inspect and compile the app without applying migrations or creating local database state.
Concepts in plain English
This capstone proves the web app layers can work together without losing testability.
The chapter uses these concepts:
- Routes, controllers, templates, static assets, uploads, sessions, forms, auth helpers, migrations, seeds, and tests.
- SQLite locally, with deployment notes for other databases.
- Debug mode, watch mode, and route inspection.
- Read-only self-check before serving or changing database state.
- Keeping model behavior, controller response policy, and template rendering in separate places.
Vocabulary and commands
This chapter consolidates MVC, data, auth, forms, and testing concepts.
Guided example
Open examples/learn/37-capstone-mvc/project_journal:
codericochet.toml
config/routes.rco
app/Controllers/JournalController.rco
app/Models/JournalEntry.rco
app/Views/journal/index.html
public/app.css
db/migrations/0001_create_journal_entries.up.sql
db/migrations/0001_create_journal_entries.down.sql
db/seeds/001_journal_entries.sql
tests/JournalEntryTest.rco
The manifest declares an MVC app, static assets, escaped views, and a local SQLite development database:
code[web]
mode = "mvc"
routes = "config/routes.rco"
[web.views]
escape = "html"
[database.default]
adapter = "sqlite"
url = "db/development.sqlite3"
Routes are still postfix:
codeGET "/" JournalController "index" route
GET "/entries" JournalController "index" route
GET "/entries/export" JournalController "export_json" route
GET "/entries/status" JournalController "by_status" route
GET "/entries/:id" JournalController "show" route
POST "/entries" JournalController "create" route
POST "/entries/:id" JournalController "update" route
Validate the route table:
coderco routes examples/learn/37-capstone-mvc/project_journal
Expected output:
codeGET / JournalController#index
GET /entries JournalController#index
GET /entries/export JournalController#export_json
GET /entries/status JournalController#by_status
GET /entries/:id JournalController#show
POST /entries JournalController#create
POST /entries/:id JournalController#update
The model maps to the migration table and adds one display method:
codeJournalEntry Model Subclass
"journal_entries" Table
"id" Accessor
"title" Accessor
"body" Accessor
"status" Accessor
"mood" Accessor
"created_on" Accessor
[
self title.get
" [" concat
self status.get concat
"]" concat
] "label" Method
end
The index action uses database results defensively, so an uncreated local development database still gives the view empty arrays and counts during compile-time checks:
codeJournalEntry default_page dup ok? if
value entries var
else
drop
entries array
end
JournalEntry count_records dup ok? if
value totalEntries var
else
drop
0 totalEntries var
end
Run the doctor:
coderco doctor examples/learn/37-capstone-mvc/project_journal
Expected output includes:
codeOK manifest: package learn_project_journal
OK project kind: MVC app
OK routes: 7 route(s)
OK MVC app build: controllers, models, routes, and views compile
Doctor found no issues.
Inspect migrations without applying them:
coderco migrate status examples/learn/37-capstone-mvc/project_journal
Expected output:
code[ ] 0001_create_journal_entries
Run the MVC tests:
coderco test examples/learn/37-capstone-mvc/project_journal
Expected output:
codePASS JournalEntryTest.testEntriesCollection
PASS JournalEntryTest.testEntryLabel
2 tests, 0 failed
The tests exercise model behavior without creating the database:
codeJournalEntry new
"Manual outline" swap title.set
"published" swap status.set
label
"Manual outline [published]" assert_equals
How to read the example
Read the capstone from the outside in. Start with the user command or app surface, identify the data boundary, follow the core transformation, and then check tests and packaging. The point is integration, not new syntax.
Try it
Add a "tag" column:
- Add
"tag" AccessortoJournalEntry.rco. - Add
tag text not null default 'general'to the up migration. - Add
tagdata to the seed file. - Print
{ entry get "tag" at }in the view. - Add a model test that sets
tagand verifies the value.
Then run:
coderco routes examples/learn/37-capstone-mvc/project_journal
rco doctor examples/learn/37-capstone-mvc/project_journal
rco test examples/learn/37-capstone-mvc/project_journal
When you intentionally want local database state, apply migrations and seeds:
coderco migrate apply examples/learn/37-capstone-mvc/project_journal
rco seed examples/learn/37-capstone-mvc/project_journal
Those commands create or update db/development.sqlite3, so they are not part
of the default the Learn self-check manifest.
Check your understanding
- What earlier chapters does this capstone combine?
- What is the user-facing entry point?
- What data boundary does the app use?
- Which tests or checks prove the capstone still works?
Common mistakes
- Mixing scaffold convenience with production assumptions.
- Letting controller, model, and template responsibilities blur.
- Running
migrate applyorseedjust to check syntax. Useroutes,doctor,migrate status, andtestfirst. - Treating a scaffold login loop or local seed data as production policy.
- Returning raw database failures to users without choosing response status and message shape deliberately.
- Forgetting that route action args bind request data and context according to the MVC binding rules.
Safety notes
The main self-check path is read-only. migrate status inspects migration
state, while migrate apply, migrate rollback, and seed intentionally
change the local development database. The down migration contains the rollback
SQL needed for a reversible schema, but this chapter does not run it.
Production guidance
Production MVC apps should review database adapter settings, migration policy, session secrets, authentication and CSRF policy, upload limits, static assets, logging, and capability declarations. Keep environment-backed secrets out of the repository, and keep seed data separate from production migrations.
Use rco serve --watch for local development and rco serve --debug when you
want request-fault pause reporting before HTTP 500 responses.
Reference links
docs/learn/chapters/23-mvc-first-app.htmldocs/learn/chapters/24-routes-controllers-and-responses.htmldocs/learn/chapters/25-templates-static-assets-and-uploads.htmldocs/learn/chapters/26-data-active-record-and-migrations.htmldocs/learn/chapters/27-sessions-forms-auth-and-passwords.htmldocs/learn/chapters/32-debugger-dap-lsp-and-editor-tools.htmldocs/reference/guides/web-and-data.html
What you know now
You know how the Ricochet web stack fits together in a complete app: manifest, routes, controller actions, model mapping, templates, static assets, migrations, seeds, route inspection, project doctor checks, and MVC tests.
Next step
Continue to Chapter 38: Capstone Packaged GUI App.