Your MovieDB gained traction and is now a popular tool among cineasts. This comes with a downside: You noticed a degrading content quality, which only can be contained by introducing a new moderation process. While everyone can keep adding new movies, they should at first be drafts that require a mandatory review by an administrator before publication.
Important
Work on this lesson in
buildermode.
Learning goals
- You can explain what a state machine is β states, transitions, guards, callbacks β and recognize when a record's lifecycle should be modeled as one instead of as a set of boolean flags.
- You can implement a state machine in plain Ruby and Rails: a state column, transition methods that only allow valid transitions, and callbacks on transition.
- You can implement the same with a gem like
rails_state_machineand explain what the gem adds over the hand-written version. - You can make validations depend on the record's current state.
- You can tie authorization and the UI β which records a user sees, which transitions they may trigger β to the state.
- You test state-dependent behavior at the right level: end-to-end for the business process, unit tests for transitions and validations, with factory traits for records in a given state.
- You know that multi-step "wizard" forms are a common use of state machines.
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.
- π Wikipedia: Finite-state machine Show archive.org snapshot β states, transitions, guards in the abstract; the first sections are enough
- π rails_state_machine Show archive.org snapshot β our gem, which we standardized on: README shows the DSL, callbacks and validations per state
- π state_machines-activerecord Show archive.org snapshot and AASM Show archive.org snapshot β the two gems you'll meet in other projects; skim their READMEs to see the same ideas in a different DSL
- π factory_bot: traits Show archive.org snapshot β one factory per state, as variants
Requirements
Movies in MovieDB should have one of the following workflow states:
draftpendingpublisheddeclined
A movie always begins as a draft and then transitions through the states as it's getting reviewed. This could be a typical state flow for a movie:
stateDiagram-v2
[*] --> draft
draft --> pending
pending --> declined: Reason
declined --> pending
pending --> published
Changes to the authorization
Change the visibility rules (Consul powers) so:
- All users can see published movies of other users.
- All users can see the movies they created themselves, regardless of their state.
- An admin can see movies from other users that are published, pending or declined.
Changes to the UI
We will now add some additional controls to transition a movie between states:
- When a user views one of her draft or declined movies, she sees a button Submit for Review on the movie's show view. This changes the movie's state to
pending. - When an admin opens a
pendingmovie by any user, she sees two buttons Publish and Decline. - Before the admin may decline a movie, she is required to enter a declining reason. The declining reason should be entered in a form like the other forms in your app, with the same form round trip and controller handling. Consider whether you want to implement this inside an existing
:moviesresource or in a new one just for the declining form. - In addition, when an admin edits a movie by any user, she can choose any state from a select box, regardless of the movie's current state. Note that any transition constraints and callbacks will not be enforced when the
#stateis set directly. We only do this to allow admins to correct user errors.
Changes to the validity of movies
States of a given state machine are frequently used as conditions for our model validations. Your movies' validation logic should also be adjusted:
- Movies in the
draftstate should already be valid if they only have aName - Additional fields like the
Release DateandDurationshould be only validated in thepending,declinedandpublishedstate -
declinedandpublishedmovies should track aModerator ID(the ID of the admin that reviewed the movie), which must also be validated in these states
Exercises
Make two implementations of the requirements above:
- Using only Ruby/Rails and no state machine gems
- Using a gem like rails_state_machine Show archive.org snapshot
Make sure that you have tests for all the changes you make. Note that your E2E-Tests should rather focus on the effects of the required moderation process than on details of your technical implementation.
Tip
factory_bot offers traits Show archive.org snapshot to define variants of a factory. Your default factory should create records in a state that is useful for the vast majority of tests.
Tip
We often use state machines to track the state of "wizard forms", i.e. long forms that are broken down to multiple smaller screens.