Thursday, October 24, 2013

Ultra Light & Maintainable Wizards in Rails

Recently I worked on Rails applications that had major requirements for web wizards, and they were built incrementally and iteratively with both junior and senior developers contributing further steps to the wizards over the lifetime of the projects. I presented my approach this past summer at the Montreal.rb user group (Update: and RailsConf 2014 ), and I am writing this blog post to provide more details and code examples.


Late last year and early this year when I scoured the net for web wizard approaches in Rails and wizard libraries, I noticed many different variations, happening across many dimensions, like the number of controllers/actions and how data is persisted across wizard pages.

Examples of controller/actions variations:
  • One controller per wizard page
  • One action per wizard page
Examples of Data persistence variations:
  • Save data in the session on every page and create an ActiveRecord when finishing wizard
  • Save data in a different ActiveRecord per wizard page
  • Accumulate data in a hidden field and use on the last step to create an ActiveRecord
  • Save data in one model with the help of a state machine
  • Manage saving data in the controller
Some approaches try to be modular by having multiple controllers or multiple actions. Others try to be light on session use by saving to the database directly.

Here is my personal feedback on the approaches above:
  • A wizard is editing one resource through multiple steps, so why are there multiple actions or controllers involved in building/updating that single resource? That violates REST. Not that it matters to break guidelines except for the pragmatic fact that it makes it more complex for developers and future team members to maintain the code.
  • A single ActiveRecord is being edited across steps even if some steps edit sub-models within it, so saving different ActiveRecords is an over-complication. That violates Cohesiveness by fragmenting the focus of the data persistence of a wizard.
  • Saving data to the session has server scalability concerns and complexity concerns. That makes Scalability harder.
  • Accumulating data in a hidden field increases complexity. That goes against Simple Design.
  • Managing data persistence details in the controller does not divide responsibilities correctly between the controller and model. That violates MVC, making it harder to maintain the code by developers and future team members.
  • A Wizard is simply editing different facets (data views) of a model, and not necessarily moving a model along a Business State Machine. The steps for entering/editing a model are also a View/Presentation concern, so even if a state machine is involved, it is not a model concern. The model might have genuine business states, but are usually only few compared to the steps of the wizard, which might adjust for usability (presentation) reasons, so although there might be overlap between wizard steps and model business states, one must not mix them as that couples the two incorrectly and unnecessarily, complicating maintainability greatly (with much cost to pay by developers over a year). That violates MVC, abuses State Machine, and worsens Maintainability.
So, where does that leave us?

I ended up solving the problem on both Rails projects with a new simple and clean approach following some concrete goals.
Goal Summary
  • Simplicity
  • Maintainability
  • Scalability
  • Proper MVC
  • Proper REST
  • Proper Object Oriented Design (respect for coupling and cohesion principles)
Starting with this end in mind, one way to think about the wizard is that it is nothing but a model builder, akin of the good old Builder Design Pattern. Without a wizard, you usually have one form that a user fills in to create a model. With a wizard, the model is created in multiple steps however, so the REST resource edited in each step is a model part (aka model data view or model step).

Wizard Flow
  • Starting the wizard creates the model and allows the user to edit the first part (data view) of the model
  • Each subsequent step enables the user to edit more parts of the already created model


Guidelines


  • REST: Following REST, the resource is nothing but the good old Model itself, except a wizard focuses on one part of the model at a time, so more accurately "model part" is the RESTful resource for wizards.
  • MVC: Following MVC, each step can be routed to an ActiveRecord representing a part of the Model. To figure out which part, the Controller relies on the ID parameter, indicating the part (step) being edited in addition to model_id. Also, each model part is a subclass of the main Model ActiveRecord to facilitate connection to the database columns with the least code possible while retaining separation of concerns (an improvement would be to use delegation from a wrapper model, useful as a future refactoring step for the design if the coupling to ActiveRecord becomes undesirable later on. In my experience, that has not been an issue, so I elected to rely on single-table-inheritance style of database mapping reuse as you will see below)

With the two guidelines above, the developer has a complete template for the implementation with everything falling into place, and the rest is details. Let's get down and dirty to illustrate the concepts above with code!

Routes ProjectsController is only responsible for creating a project. Afterward, ProjectPartsController manages the project parts as wizard step resources (REST). The routes above will produce this pattern for moving along a wizard (editing a model part): /projects/:project_id/project_parts/:id/edit

ProjectsController
ProjectsController redirects to the first project part (wizard step) after project creation

ProjectPartsController
ProjectPartsController treats model parts (wizard steps) as the resource that can be edited/updated along the way. params[:id] contains the ID of the resource, which is the model part name (aka wizard step name). Rendered views can then match that ID name. The implementation above is one way to do it. The code could be written differently and optimized depending on needs (such as moving some private methods to a Helper and sharing them with the views if needed, like the step and next step for example, which can be displayed visually on the page for labelling and navigation)

Project model
The main project model contains associations and common logic, and then each wizard step can have its own sub-model if necessary to manage step specific validations without having the validations interfere with other steps. Sub-models also manage data loading upon showing the wizard step edit page (via after_initialize) or after updating (via after_save)

Project::BasicInfo sub-model


Project::Detail sub-model

Views

Note how the views match the wizard steps.

View Forms
All view forms (e.g. basic_info.html.erb) will have the form template above, which is agnostic to which model part is being edited. On one project, the developer actually created a helper (e.g. project_part_form_for) that hides all the details and can be unobtrusively reused across all wizard view pages.

Sub-Models

Note how only the wizard steps that require validation logic and special on edit or on update logic need sub-models.

I hope you found this example implementation helpful in your endeavour to implement Ultra Light & Maintainable Wizards in Rails. Note that it is a simpler version of what I have implemented for my clients, which often grew with their rising needs over time, stretching the implementation without much strain due to its simple design and adherence to sound software development guidelines, such as REST and MVC. This approach really scales well since it allows one to add extra authorization checks in the controller without a hitch and extra hooks/validations on the sub-models depending on need. Additionally, it is compatible with the model having a state machine or following the State Design Pattern as implemented on one of my projects.
Now, no write-up would be complete without a Gotchas section, so let's get to that.

Gotchas
  • Validations live in sub-models. What if one wants to have a second mechanism for editing the model in one page utilizing the same validations? Simple. Chop the validations off into Modules, mix each of them into one of the wizard sub-models and then mix them all into a new sub-model to be used as the model for the second editing page. This approach can also be used to reuse validations for the last step of the wizard and re-run all the validations once again at the end if needed.
  • How do you skip a step based on certain rules? You need to build a check into the controller that sees if a certain model part needs to be skipped (via a skip? method for example on the sub-model). If so, the controller simply redirects to the following step. Covering the details of this is outside the scope of the blog post, but I am sure you can figure it out.

On the first project that I added the wizard to, the CTO was happy to see that the junior developer was able to add new steps to the wizard in record time after spending 5 minutes with me going over the details of how it works. On that same project, a senior developer who joined later on and was working remotely figured out the wizard design in no time and sent me congratulations on how flexible and nicely designed it was. The same happened on the other Rails project I used the wizard approach for, which facilitated growing a wizard from about 5 steps to 15 (with some interesting custom logic on step branching). So, you can rest assured this approach will work well and scale for your Rails projects.

6 comments:

Douglas Lee said...

Nice blog. I've just started looking into Ruby, Rail, and Java. I used to develop in C# eventually moved into sw management. This has been a good read to catch up on some of today's technologies since I wish to do some development again. Thanks

Anonymous said...

Enjoyed your talk at Railsconf and am referring back to your stuff now for a problem in need of a wizard solution. Thanks again! Neil@Shopify

Unknown said...

Thanks Andy! I have been thrown into rails development and this pattern has really helped me understand different ways of presenting.

I could ask a million questions, but I will start with one small one. :) What is .editable? in your parts controller? I was thinking this would be part of ActiveRecord but I can't seem to find a reference to it. I also thought it might just be a need to be implemented property that said the model was ready for updates?

Thanks again!

one more blog about something said...

The smartest maintainable approach for the wizard hell Ive seen. Great talk! Thank you for sharing. I hope I can see that great gem working any time soon. Do you have any idea how this solution could be packed in the gem? I would love to engage in this project.

Andy Maleh said...

Just a quick note that I added the talk video and slides from RailsConf 2014's presentation under this blog post:
http://andymaleh.blogspot.ca/2014/03/presenting-at-railsconf-2014.html

Luke Adams, you are right about ".editable?" being a property (a method really) that returns whether the model in its current data state is editable or not. The actual example I extracted that code from (and forgot to take out or include editable?'s implementation as part of) is a business rule whereby a project goes into review once submitted and thus does not any allow further modifications until the reviewer has completed the review process. Interestingly, these business rules, which are orthogonal to presentation, make use of the State Design Pattern (a more modular and flexible version of the State Machine pattern originally created by the Gang of Four) to elegantly encode the different state business rules, orthogonally to the Wizard step-by-step presentation rules leading to extremely easy to understand and maintain code (saving days worth of work).

Unknown said...

Hey Andy, just sending big thanks.
Your presentation describing all the types of pitfalls while designing wizards, and then your approach saved me countless hours of pain. I really learned a lot and it helped me build an app for my family business.

Thanks Man!
Victor.