RailsConf 2014

Ultra Light and Maintainable Rails Wizards

Ultra Light and Maintainable Rails Wizards

by Andy Maleh

In this presentation titled 'Ultra Light and Maintainable Rails Wizards', Andy Maleh discusses the common yet often poorly implemented feature of wizards in web applications, particularly within Rails. With a focus on achieving maintainability and simplicity, Maleh emphasizes the importance of writing clean wizard code that avoids typical pitfalls like copy-pasting, complicated controllers, and managing excessive session data. The primary goal is to create wizards that improve user experience without overcomplicating the underlying codebase.

Key Points Discussed:

  • Purpose of Wizards: Wizards play a crucial role in web applications by breaking down complex forms into manageable steps, preventing user overwhelm, and providing clarity which aids in completing tasks.
  • Common Issues: Many implementations resort to excessive copy/pasting across multiple steps, leading to maintenance nightmares. Coding for wizards often ends up violating REST principles and introduces complexity in session management.
  • Implementation Goals: Maintainability, productivity, and adherence to REST principles are critical for achieving effective wizard implementations.
  • Typical Approaches: Maleh critiques several approaches including one controller per step—leading to code repetition and scalability issues, and a session accumulation method that complicates controllers and violates the MVC paradigm.

Illustrative Case Study:

Maleh shares his experience working with a startup named Early Shares, which required onboarding wizards for its investment platform. The challenges involved maintaining the codebase with minimal resources led him to explore cleaner architecture patterns that would facilitate better maintenance and scalability over time.

  • Ultra Light Approach: The speaker introduces a novel approach where he designed wizards as a series of steps managed by presenters rather than large controllers. This involved using a main model that tracks progress through nested model steps, leading to cleaner code and separating business logic from view concerns.
  • Main Benefits: His methodology emphasized focusing on user experience while separating the steps into manageable components, using REST principles effectively while minimizing risky session reliance. This structure led to significant reductions in code complexity and improved maintainability.

Conclusion and Takeaways:

  • Optimization through Design: Adopting design patterns such as the Builder pattern can successfully help organize the wizard process and maintain clarity in code. Each step not only enhances user experience but also avoids confusion at the code level.
  • Future Thoughts: Maleh hints at developing a gem named 'ultralight wizard' that encapsulates these ideas, aiding developers in implementing wizards more efficiently and maintainably in their Rails applications.
  • Final Note: The talk reiterates that maintaining a clean and clear separation of concerns in any codebase significantly contributes to its long-term sustainability and ease of use for future developers.
00:00:16.720 Sorry everybody, I lost the slides and had to reconstruct them right now, just like in 10 minutes.
00:00:23.920 Ultra Light and Maintainable Rails Wizards. Who has written a wizard in their lifetime?
00:00:31.199 Okay, it’s almost like the most common web use case, yet it's the least appreciated when it comes to providing patterns for writing good code.
00:00:37.840 Many people struggle with writing maintainable wizards. A lot of the time, people write multi-step wizards where they end up doing a lot of copy-and-paste between the steps or across multiple controllers.
00:00:44.000 This makes it a significant problem for maintaining that code a year or two later. Every codebase is meant to be maintained for at least a year, and the maintenance cost is what's really expensive.
00:00:55.280 It's not just about writing a wizard in two weeks, but about whether I can maintain it cheaply over a year. That is really why I'm giving a talk on this subject.
00:01:06.080 So, let me give you an overview of what I'll cover: why we use wizards, an example of wizards, some implementation goals, the myriad wizard implementations out there, and finally, my recommended approach for creating ultra-light and maintainable wizards.
00:01:17.520 First of all, we don't want to overwhelm users with an extensive form of information, similar to those painful government forms we have in Canada.
00:01:28.000 Computers should enable people to make processes better than physical paper. One way to tackle this problem is to divide it into multiple steps in a wizard.
00:01:42.240 Additionally, it's about simplifying the workflow into digestible steps, similar to how you would enjoy a protein shake. Often, it gives you the opportunity to provide more explanation about what each form does by presenting more information across multiple pages, like what they do with TurboTax.
00:01:55.600 I should ask, who raised their hand to indicate they have experience with wizards?
00:02:03.440 I had a software architecture role at EarlyShares a couple of years ago where I helped launch their site. EarlyShares is similar to Kickstarter or Indiegogo, but focuses on allowing people to crowd-invest in businesses.
00:02:56.959 The website was built quickly to comply with new U.S. laws allowing crowd investment. As part of the website, they needed a couple of onboarding wizards—one for investors and one for business owners.
00:03:11.360 However, their business was bootstrapped, and we were only two developers: me as a senior and one junior developer, along with a CTO and a designer.
00:03:16.879 The CTO wanted us to move super fast, and I was brought in as their Rails expert. Interestingly, I had not written a wizard in about four or five years—since my Java development days.
00:03:37.760 So, when I started tackling this problem in Ruby, I checked Google guides and Stack Overflow, but none satisfied me. Now, let’s discuss what I found.
00:03:53.760 The basic wizard example consisted of four steps: Step one, collect basic info; Step two, input details; Step three, upload document content; and Step four, preview before finishing the wizard.
00:04:06.239 Once completed, it presents a summary like a landing page for the proposed business investment.
00:04:22.240 The goals I aimed for were to ensure that the Rails server persisted progress at each step without relying on JavaScript client-side tricks. I also wanted the implementation to be RESTful, which is a common challenge when building wizards.
00:04:40.480 Moreover, I aimed to stick to MVC and object-oriented principles because we wanted to ensure that the code remained maintainable for future developers.
00:04:48.799 Additional non-functional requirements included productivity, as the CTO emphasized the need to move quickly. But I tend to pay attention to details and design concerns, which could slow things down for good reasons.
00:05:01.360 Ultimately, the story had a happy ending with the project achieving maintainability for both junior and senior developers, including a senior developer in Brazil who joined later.
00:05:14.960 Performance and security concerns were also top of mind, which are basic considerations whenever building any features.
00:05:31.680 One approach I saw was to create one controller per wizard step, thus creating a REST resource for each step.
00:05:42.240 This resulted in multiple controllers, sets of views, and helpers, where each controller redirects to the next. For validations, you could implement either a single ActiveRecord with conditional validations or multiple ActiveRecords.
00:06:00.960 So, I ask, who here could find concerns with this approach or suggest improvements?
00:06:06.960 A volunteer noted that managing a multitude of controllers could pose a challenge.
00:06:15.840 This raises the issue of repetition; a lot of code ended up being repetitive across the controllers.
00:06:26.079 For example, loading the same resource and running validations resulted in a lot of duplicated code. A previous developer had tried to implement a feature after I joined the team and found it took months to accomplish, as they grappled with the original design.
00:06:55.360 This highlights the importance of a well-structured development approach to enable quicker implementation of features. My aim was to employ the Ultra Light Maintainable Wizard approach I had discovered earlier.
00:07:14.559 This method proved effective, allowing us to develop within seven days, with tests prioritized early in the process.
00:07:20.960 We achieved this because we no longer had so many controllers, which resulted in less code and therefore reduced testing requirements.
00:07:28.960 Let’s discuss another approach—using one controller for all wizard steps.
00:07:34.960 This approach still relied on ActiveRecords, but could lead to repetitive code across actions.
00:07:42.000 It was an improvement over the first approach but still had serious shortcomings, mainly the repetitiveness in code.
00:07:51.680 Using presenters, an abstraction layer between ActiveRecord and the controller, can be beneficial to have separate validations for each wizard step.
00:08:00.720 But it still presented challenges, as the controller often ends up repeating similar validations.
00:08:10.080 Now, who here has implemented a wizard with session accumulation? How did that work out for you?
00:08:25.760 One participant shared their experience, mentioning difficulties managing session information across multiple controllers.
00:08:35.200 Indeed, reliance on session storage can lead to complexity in controller code, breaking the MVC model.
00:08:45.840 It makes managing validations complicated and can lead to duplicated code.
00:08:54.080 Hidden value accumulation was another method, where values were hidden into fields on the submission page.
00:09:05.760 While this method is stateless and avoids session issues, it can expose sensitive user information unnecessarily.
00:09:15.360 Using a state machine can address some challenges of wizard workflows; however, it comes with its own issues.
00:09:26.840 You must integrate validations directly connected to different states, which can complicate the model.
00:09:36.080 But in general, a state machine can offer a better compromise over other wizard implementations.
00:09:52.560 Conditional validations arise when there is a need to validate based on the current step.
00:10:05.920 The approach has merits, but with the added complexity, it risks obscuring business logic.
00:10:19.600 The idea should focus on making the user's experience seamless and easy to understand. For that reason, keeping concerns separate is key.
00:10:33.440 This way, one can avoid overwhelming the model or the controller with mixed responsibilities.
00:10:40.480 There are various gems available that can assist in simplifying wizard creation.
00:10:49.040 However, they may not effectively meet all goals related to RESTful design, MVC and maintainability.
00:11:06.159 Now, let’s subject the wizard process to a comprehensive review.
00:11:20.000 So, I thought, let’s attempt to solve this from scratch using object-oriented principles and domain-driven design.
00:11:30.000 Who here is familiar with 'Domain-Driven Design'? It’s a great resource for learning how to approach real business problems with object-oriented design.
00:11:48.680 Refocusing on wizards, their highest goal from the user perspective should always be to streamline workflow.
00:12:04.639 Yes, to simplify and present information in an organized fashion.
00:12:08.480 The overall goal of a wizard is to produce an object through a sequence of steps—almost like the builder design pattern.
00:12:25.000 Each step of the wizard is essentially a view of a part of the larger object being created.
00:12:39.880 This can be very straightforward within REST resources, as it separates concerns effectively.
00:12:51.420 For instance, a wizard step can be made a nested resource of a wider object, thus simplifying routing.
00:13:04.080 Furthermore, this design avoids conditional validations, which complicate models and make them harder to read.
00:13:19.440 Instead, separating validations and associated logic within the presenter enforces a clearer structure.
00:13:30.000 Less embedded logic ensures that the code remains clean and maintainable over time.
00:13:39.920 Each wizard step is designed as a separate presenter—each handling only its validation and associated behavior.
00:13:55.920 This encapsulates related concerns and isolates each step, reducing the chances of an unwieldy codebase.
00:14:07.920 Having a project controller that oversees the wizard process provides an avenue for control without overloading responsibilities.
00:14:21.920 By following these principles, we can create ultra-light and maintainable wizards that promote developer efficiency.
00:14:35.920 Thus, as you develop your wizards, remember to prioritize clarity and separation of concerns.
00:14:46.000 In conclusion, I discussed why to use wizards, provided an example, outlined implementation goals, and concluded with my approach.
00:14:56.000 My goal is to ensure that the code is not only maintainable today but also a year from now while minimizing training efforts.
00:15:07.120 Please feel free to connect with me, and thank you for your attention.
00:15:15.960 I'm looking forward to monitoring progress on projects, and I'm here to answer any questions.
00:15:28.000 Once again, my name is Andy Maleh, VP of Engineering at Big Astronaut.
00:15:39.320 We’re a fully remote consulting company, and we're currently hiring. Thank you, everyone!