Not every interaction maps cleanly to an ActiveRecord model. Form models hold the validations and logic of a single screen, like a sign-up form, without overloading your core models.
Important
Work on this lesson in
advisormode.
Learning goals
- You can explain why logic that belongs to one screen — its validations, callbacks, virtual attributes — should not live on the core model, and what goes wrong when it accumulates there.
- You can explain what a form model is and how ActiveType gives it the full ActiveRecord API, whether it is backed by a database record or by nothing at all.
- You can decide when a form model should extend a record (
ActiveType::Record[User]) and when it should stand alone (ActiveType::Object). - You can implement one user interaction as a form model — validations, callbacks, side effects — with a controller that stays simple.
- You can model an interaction that touches several records, such as merging two of them, as a form model without its own table.
- You can judge whether a piece of behavior belongs in a core-model callback, in a form model, or in the controller.
- You can organize a growing code base with namespaces that express which classes belong together.
Resources
Read what's new to you, skim what's familiar, skip what you already master. Stop when you can meet the learning goals.
Your agent can also generate an overview, a tutorial or an explanation for anything here, tailored to what you already know. Just ask.
- 📘 Growing Rails Applications in Practice ( Leanpub Show archive.org snapshot , also in our library) — read or re-read: "New rules for Rails" (Beautiful controllers, Relearning ActiveRecord, User interactions without a database) and "Creating a system for growth" (Dealing with fat models, A home for interaction-specific code, Extracting service objects, Organizing large codebases with namespaces)
- 📄
ActiveType README
Show archive.org snapshot
—
ActiveType::Record[Model]for form models that extend a core model,ActiveType::Objectfor interactions without a database - ▶️ Do You Need That Validation? Let Me Call You Back About It Show archive.org snapshot — Tobias Pfeiffer, Ruby on Ice 2019; why validations and callbacks on the core model become a problem
Note
We no longer use the
Asnaming convention when extending models withActiveType::Record[User]. Pick the noun that best describes the extended class, and keep the parent class as a namespace:class User class RegistrationForm < ActiveType::Record[User] ... end end
Talk with your mentor about the motivations behind form models.
Exercises
Go through the repos of Cards and/or MovieDB and apply what you learned:
- Create a form model backed by a database
- Have two different forms to create a user: One for public sign up, one in a private admin area
- The sign up form sends a welcome e-mail and checks for password policy, the admin form doesn't.
- If you are struggling with this, just have a method
send_welcome_emailthat does nothing. If time allows, try changing this to send a simple e-mail once you are done with everything else in this card.
- If you are struggling with this, just have a method
- Implement the public sign up form once as an
ActiveType::Objectand once as anActiveType::Record[User]. What are the pros and cons of each approach?
- Create a form model not backed by a database
- Implement the login form using
ActiveType::Object - Implement a screen to merge two movies.
- Flat attributes are copied from the source movie if it is missing on the target movie.
- The poster is copied from the source movie if the target movie has no poster yet.
- Lists of actors are merged, but actors found in both lists are only kept once.
- Showtimes are merged as well (hint: this is easier than merging actors), discarding duplicates.
- The first movie is destroyed after successfully merging into the second movie.
- Implement the login form using
- Find and talk about examples where a method should better live in a callback and vice versa
- Find a composition of classes and move the child classes into the namespace of the container class.
- Move a group of related classes into a namespace.