The engine convention already here
Four files in this application declare a Rails engine convention. Twenty-three models decline it. That's the whole starting point.
The declaration
# app/models/catalog.rb
module Catalog
def self.table_name_prefix
"catalog_"
end
end
Four namespaces do this, Catalog, Community, Enrollment and Partners, and each names a prefix. Identity declares the module and no prefix.
table_name_prefix is the Rails engine idiom. It's what isolate_namespace sets up for you, and its purpose is to keep an engine's tables from colliding with a host application's. Adopting it in a monolith means adopting an engine convention without an engine. That's a reasonable thing to do: a catalog_courses table says which context owns it.
What happens in practice
USES the prefix (1):
Community::Comment -> community_comments
OVERRIDES it (23):
Catalog::Category -> categories
Catalog::Challenge -> challenges
Catalog::Course -> courses
Catalog::Lesson -> lessons
Catalog::Quiz -> quizzes
Catalog::QuizOption -> quiz_options
Catalog::QuizQuestion -> quiz_questions
Enrollment::Badge -> badges
Enrollment::Bookmark -> bookmarks
Enrollment::Enrollment -> enrollments
Enrollment::ExperiencePoint -> experience_points
Enrollment::LessonCompletion -> lesson_completions
Enrollment::LessonNote -> lesson_notes
Enrollment::Level -> levels
Enrollment::QuizAttempt -> quiz_attempts
Enrollment::UserBadge -> user_badges
Partners::ApiToken -> partner_api_tokens
Partners::CourseSponsorship -> partner_course_sponsorships
Partners::HandoffNonce -> partner_handoff_nonces
Partners::Inquiry -> partner_inquiries
Partners::Partner -> partners
Partners::Referral -> partner_referrals
Partners::Role -> partner_roles
Each of those twenty-three declares self.table_name explicitly, and an explicit table name wins over a prefix.
One model out of twenty-four honors the convention, and it does so by not mentioning it. Community::Comment has no self.table_name line, so the prefix applies and the table is community_comments.
flowchart TB
A["4 namespaces declare table_name_prefix"] --> B["the Rails engine idiom, in a monolith"]
B --> C["23 models declare self.table_name and override it"]
B --> D["1 model omits it and is prefixed"]
D --> E["Community::Comment -> community_comments"]
C --> F["the convention was adopted, then declined, one model at a time"]
What this is evidence of
It isn't a bug. Every one of those twenty-three names is correct: courses, lessons and enrollments are good table names, and catalog_courses would be worse to type in a psql session.
It's evidence of a convention adopted without a decision behind it. The framing of the mid-level course "Leader: ADR process" applies directly: the prefix declarations say what to do, nothing says why, and so each model author made the call again, twenty-four times, and reached the same answer twenty-three times.
The one exception doesn't read as a disagreement either. Community::Comment was written on 30 August, the same day as the first four Partners models, and all of those declare their table names. Nothing in the code says why this one doesn't.
That's the real finding: the current table naming isn't a convention. It's what's left of twenty-three independent decisions plus one omission.
The three honest options
Delete the prefix declarations. They take effect in one model out of twenty-four, and otherwise only mislead a reader into thinking tables are prefixed. Rename community_comments to comments and the codebase becomes consistent.
Honor them. Delete the twenty-three self.table_name lines, write twenty-three renaming migrations, and every table announces its context. It's expensive, and it makes the schema self-describing.
Leave it and write down which one is true. One line in MEMORY.md, one ADR: "tables are named for what they hold, not for their context; the table_name_prefix declarations are vestigial."
Any of the three beats leaving it unstated, because otherwise a reader loses ten minutes every time they wonder whether a table is prefixed, and the answer is "usually not, except once."
This application took the third option on 2026-09-28. MEMORY.md now says tables are named for what they hold, that a new model declares self.table_name, and that the four declarations are vestigial.
Why this opens a course about engines
The question underneath is the one this whole course asks: how much of the engine model do you want, and what does each piece cost?
This application took one piece, the table prefix, and then rejected it in practice. That's a specific answer to a specific question, reached without anyone stating either. Lesson 2 covers the rest of the model.
Key takeaways
- Four namespaces declare
table_name_prefix, the Rails engine idiom for avoiding table collisions. - Measured on 2026-09-28: 23 models override it with an explicit
self.table_name; only 1 honors it, by omission. - None of the twenty-three names is wrong; this is a convention adopted without a decision behind it.
- Three honest options: delete the declarations, honor them, or write down which is true.
- Today a reader loses ten minutes whenever they wonder whether tables are prefixed.
Keep going with lesson 2
What an engine actually is
An engine is a Rails application inside another; isolate_namespace prevents naming collisions but leaves constant references open.
A free account also gives you the quizzes, your progress and notes, and the discussion.