Wizards have been common in web applications since the dawn of the Internet, with the most popular example being the Shopping Cart, yet many struggle with writing wizard code effectively, resulting in a huge untraceable rat's nest of copy/paste code. In fact, many implementations violate REST and include Fat Controllers as well as overly complicated wizard-step management, data, session, and validation code. This talk covers a better way that yields Ultra Light and Maintainable Rails Wizards!
12. Wizard Implementation
Goals
• Rails Server must persist progress on every step
o JS Client-Side Persistence is Out Of Scope
• REST
• MVC
• OO
• Non-Functional Requirements:
o Productivity
o Maintainability
o Performance
o Security
13. 1001 Wizard
Implementations
1. One Controller Per Wizard Step
• Create a REST resource per wizard step
• One ActiveRecord with conditional validations
• Multiple Controllers
• Multiple sets of Views and Helpers
• Have each wizard step controller redirect to the
next one on create or update
14. 1001 Wizard
Implementations
1. One Controller Per Wizard Step
Step 1
Controller
Step 2
Controller
Step 3
Controller
Step 4
Controller
Create &
Redirect to New
Create &
Redirect to New
Create &
Redirect to New
Step 1
ActiveRecord
15. 1001 Wizard
Implementations
1. One Controller Per Wizard Step
o Critique
• Redundant code across controllers
o Repetitive redirect logic
o Redundant authentication logic
o Similar model loading logic
o Similar REST actions
• Tying database tables to presentation details
16. 1001 Wizard
Implementations
2. One Action/Presenter Per Wizard Step
• Create one ActiveRecord
• Create one controller with a different new and create
action variation per wizard step
• e.g. new_step1, create_step1, new_step2, create_step2, etc…
• Create a different ActiveModel presenter per wizard step
• Have each ActiveModel presenter manage its own step
validation logic
• Bind every wizard step form to corresponding
ActiveModel presenter
• Have each wizard step action redirect to the next one
on create
17. 1001 Wizard
Implementations
2. One Action Per Wizard Step
Controller
Step 1 New
Step 1 Create
Step 2 New
Step 2 Create
Step 3 New
Step 3 Create
Step 4 New
Step 4 Create
Step 1
Presenter
Validation/Persistance
Adapter
Step 2
Presenter
Validation/Persistance
Adapter
Step 3
Presenter
Validation/Persistance
Adapter
Step 4
Presenter
Validation/Persistance Adapter
ActiveRecord
Create Step &
Redirect to New Ste
18. 1001 Wizard
Implementations
2. One Action Per Wizard Step
o Critique
• Not RESTful
• Redundant code across actions
o Repetitive redirect logic
o Repetitive update logic
• Too much presenter management code
19. 1001 Wizard
Implementations
3. Session Accumulation
o Create one ActiveRecord
o Have multiple Controllers or Actions accumulate wizard step data in the
session
o Have ActiveRecord in-memory conditional validations run on every step
o On the final step, create ActiveRecord running all validations
Step 1
Step 2
Step 3
Step 4
Accumulate
in Session
Accumulate
in Session
Accumulate
in Session
Create
ActiveRecord
20. 1001 Wizard
Implementations
3. Session Accumulation
o Critique
• Reliance on session has implications on scalability
• Controller code more complex due to managing session data
storage
• Validations defined twice, once per ActiveModel presenters used for
form validation and once in the actual ActiveRecord
21. 1001 Wizard
Implementations
4. Hidden Value Accumulation
o Same as session value accumulation except the controller manages data
coming from a request parameter
o Same pros and cons as Session Value Accumulation except that it has no
scalability implications
o NOTE: hidden value must be encrypted for security
22. 1001 Wizard
Implementations
5. State Machine
o Create one ActiveRecord
o Make ActiveRecord a state machine managing steps:
• adding a step column
• add current_step, next_step, and prev_step methods
o Different view per step
o Have single ActiveRecord manage each step validations by relying on
conditional validations. For example:
validate :phone,
presence: true,
if: lambda {current_step==“shipping”}
23. 1001 Wizard
Implementations
5. State Machine
o Critique
• Puts presentation concerns in Model (breaks MVC)
o The state machine wizard must be a layer on top of the Model. It
has nothing to do with the business model.
• Stores an extra column in the database for purely presentation-
related reasons
o Can be avoided with session storage of state, opening a different
can of worms (controller complexity)
• More complexity in declaring validations due to conditions and
potentially overloading the Model
24. 1001 Wizard
Implementations
1001. Gems
• Wizardry: state machine in model
• Wicked: clean state machine in controller (better)
but no validation support beyond conditional
validation
• Rails-Wizard-Generator: XML XML XML
• Stepper: Nice support for steps in model and
controller
25. Wizard Implementation
Goals Review
• Rails Server must persist progress on every step
o JS Client-Side Persistence is Out Of Scope
• REST
• MVC
• OO
• Non-Functional Requirements:
o Productivity
o Maintainability
o Performance
o Security
28. Ultra Light &
Maintainable Wizard
• Philosophy:
o REST resources are the model and model parts edited during a step.
29. Ultra Light &
Maintainable Wizard
• Philosophy:
o Models must manage validations without conditions by relying on step-
polymorphism
30. Ultra Light &
Maintainable Wizard
• Philosophy:
o Step view forms are maintained independently with no conditionals as
well
step1.html.erb
step2.html.erb
step3.html.erb
step4.html.erb
31. Ultra Light &
Maintainable Wizard
Model
Controller
Create
Show
Step 1 Presenter
Validations/Step Logic
Step 2 Presenter
Validations/Step Logic
Step 3 Presenter
Validations/Step Logic
Model
ActiveRecord
Core Validations
& Shared Logic
Step 4 Presenter
Validations/Step Logic
Update &
Redirect to Edit
Model Part
Controller
Edit
Update
32. Ultra Light &
Maintainable Wizard
• In a Nutshell:
o Model resource
o Nested model part resource
(e.g. /projects/project1/project_parts/basic_info)
• Step name serves as ID
• Contains validations and before/after hooks for stepping
o Controller for the model resource:
• Begins wizard by creating model ActiveRecord
• Shows produced model at the end of the wizard
o Controller for the model part resource:
• Every step represents an Edit action of a model part
• Every step submission represents an Update action of a model part
o View for model resource show page
o View for every model part presenter edit page