320 State machines [2d]

Updated . Posted . Visible to the public.

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 builder mode.

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_machine and 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.

Requirements

Movies in MovieDB should have one of the following workflow states:

  • draft
  • pending
  • published
  • declined

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 pending movie 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 :movies resource 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 #state is 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 draft state should already be valid if they only have a Name
  • Additional fields like the Release Date and Duration should be only validated in the pending, declined and published state
  • declined and published movies should track a Moderator 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:

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.

Profile picture of Henning Koch
Henning Koch
Last edit
Michael LeimstΓ€dtner
License
Source code in this card is licensed under the MIT License.
Posted by Henning Koch to makandra Curriculum (2015-09-08 17:25)