Onboarding in Rails
A multistep flow where each step is a form object, the order lives in a committed YAML file, and the gate that forces users through it needs one opt-out or the flow redirects to itself forever.
Articles on this topic
-
The onboarding gate, and the two ways it traps people
Four lines force every signed-in user into the flow. Applied to every controller they also make the flow redirect to itself, and they stop a half-finished user from signing out. Both failures, measured.
-
A Rails multistep form without a wizard gem
One string column holds the position, a list holds the order, and a controller with two actions moves between them. What resuming costs, and why deleting a step cannot strand the people standing on it.
-
Form objects in Rails, with ActiveModel
Eight lines per step: the attributes it collects, what makes it valid, and where it writes. What ActiveModel::Model and ActiveModel::Attributes hand you, and the controller line that makes strong parameters follow the form.
The signup form asks for an email and a password because anything more loses people. Everything else the product needs, a name, a workspace, a preference or two, has to be collected after the account exists. That is onboarding, and it is a multistep form with a gate in front of it.
Three parts that should not know about each other
A Rails onboarding flow goes wrong in one specific way: the steps, the validation and the navigation end up in the same place. The User model grows
validates :company_name, presence: true, if: :step_two?, and six months later a completely unrelated admin screen cannot save a user because it does not know about step two.The split here keeps them apart.
Onboarding::Registryanswers which steps exist and in what order.Onboarding::Steppairs a key with the form class that handles it.OnboardingControllerreads the current step, hands the params to that step's form, and moves on if it saved. None of the three knows what any step actually collects. The multistep flow works through the navigation half: resuming, advancing, and what happens to somebody parked on a step you delete.Every step is a form object
Eight lines, and they contain everything the step is: what it collects, what makes it valid, what it writes. The User model is not involved and gains no validation. Form objects in Rails covers what
ActiveModel::ModelandActiveModel::Attributesgive you for free, and the one line in the controller that makes the strong parameters follow the form instead of drifting from it.The gate, and the loop it creates
Forcing users through the flow is one
before_actiononApplicationController:Four lines, and applied naively they make the onboarding page redirect to the onboarding page. Every controller that must stay reachable to a half-finished user declares
allow_unonboarded_access, and fourteen of them do, including the one that signs people out. The onboarding gate is where that list gets argued, with the two failures measured rather than described.Seven steps on disk, one in the flow
Onboarding::Registryscansapp/forms/onboarding/*_form.rbfor step classes and reads the active order fromconfig/onboarding_steps.yml. Today that finds seven forms and one active key: a step class that exists but is not listed is available and switched off, waiting to be turned on.Putting the order in a committed file rather than a database table is the decision worth noticing. The admin at
/admin/onboardingreorders steps by drag and drop, and what it writes is that YAML file. So changing the flow produces a diff, goes through review, and deploys with the code. Nothing about the shape of the product lives only in production state that nobody can read back.What this is not
Not a wizard gem, and not a state machine. There is no
aasm, nowicked, no persisted workflow object: the user's position is one string column,onboarding_step, and completion is one timestamp,onboarded_at.Not conditional either. Every user sees the same steps in the same order. Branching a flow on a plan, a role or an answer given two steps earlier is a real requirement for some products and it is not what this does. The registry returns a list, and a list has no idea who is walking it.