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:

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:

  1. Add "tag" Accessor to JournalEntry.rco.
  2. Add tag text not null default 'general' to the up migration.
  3. Add tag data to the seed file.
  4. Print { entry get "tag" at } in the view.
  5. Add a model test that sets tag and 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

Common mistakes

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.

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.