 I lost the slides, I had to reconstruct them right now, right, like in 10 minutes. Ultra Light and Maintainable Rails Wizards. Who has written a wizard in their lifetime? Okay, it's almost like the most common web use case, yet it's the least undervalued with regards to providing patterns for doing, like, writing good code in order to provide for maintainable wizards. A lot of the time people write, like, multi-step wizards where they end up doing a lot of copy-paste between the steps or between a bunch of controllers. And that makes it a hell of a problem to maintain that code a year or two later. And every code base, you know, is meant to be created for a year at least. Maintenance cost is what's really expensive. It's not, you know, I can write a wizard in two weeks, but will I be able to maintain it cheaply over a year? And that's really why I'm giving a talk about this subject. So, this is going to be an overview. I'll be talking about why do we even use a wizard, provide an example, some implementation goals, the 1001 wizard implementations out there. And finally, I'll talk about what I think is a good ultra light and maintainable wizard approach. So, first of all, we don't want to overwhelm the user with a huge form of information, kind of like those government forms that we have in Canada. So, I come from Montreal, by the way. This is painful on a computer screen. Computers should enable people to do better than actual physical paper. So, one way of tackling this problem is to divide it into multiple steps in a wizard. So, also it's about simplifying the workflow into multiple steps that make him digestible, just like this protein shake. And finally, sometimes it gives you the opportunity to provide more explanation for what each form does by being able to fit more information when you break it up across multiple pages, like what they do with TurboTax. We here has filed their taxes. Yeah, I did not. I just did this, so I see who raised his hand. Okay. So, I had a software architecture gig at Early Shares about a couple of years ago where I helped them launch their site. Early Shares is kind of like Kickstarter or Indiegogo, except it's focused on allowing people to do crowd investment in businesses. And it was a website that was being built fast in order to catch up with some legal laws in the U.S. that would allow crowd investment. So, I helped them launch the site. And they had, as part of the website, they needed a couple of onboarding wizards, one for investors and one for business people. But there were other requirements, like the business was bootstrapped. We were only two developers, me as a senior and then there was a junior, with a CTO and a designer, and that's it. He wanted us to move super fast, and I was brought in as the Rails expert. So, I had not written a wizard in like four years before that, or maybe five years, like maybe since the days I did Java development. And when I started tackling this problem in Ruby, I went online and checked some Google guides and all that, or Stack Overflow, whatever. And none of that approach has satisfied me. So, let's talk about what I found. So, the wizard example, though, is basically you have four steps. Step one, you can collect basic info. Step two is details, more details. Step three is upload some document content. Step four is just preview before you finish the wizard. And then once it's done, it shows you a summary, like a landing page for the project that the business is proposing for investment. Okay, so, I mean, the goals I had was the Rails server had to persist every progress on every step. So, no like JS client side tricks, that was out of scope. I wanted it to still like be restful, which is a common issue with wizard, like building wizard, how to make them properly restful. I wanted to also stick with MVC, object-oriented principles, because we're using an object-oriented language. So, I want to make sure the code is maintainable by other developers going to the future. And then some non-functional requirements like productivity. So, that was part of the concern that the CTO had, which is he wanted us to move fast, like really, really fast. That was part of the reason why he brought me in. Well, big mistake. I pay attention to details and nice design concerns, so I will slow him down, but for good reasons. I'll slow him down, and then he'll go much faster later on. Still, the story does have a happy ending. So, maintainability by both junior and senior developers. They had one senior developer in Brazil as well, which I just remembered he was brought in a little later on. Performance concerns, security concerns. So, it's pretty basic stuff. I mean, these are the concerns that we should care about whenever we build any feature, really. So, one approach that I've seen on actual code bases, I actually saw it on a code base that I maintained on the following project after that one, was one controller for wizard step. So, you create a REST resource for wizard step, and you have multiple controllers, multiple sets of views of helpers, and then each controller redirects to the next one. So, it's something like this, and you could do it either with one active record that has conditional validations for each step, where it says, okay, if step one, validate presence of name, if step two, validate presence, blah, blah, blah. Or you could have multiple active records, but either way, who here could find concerns with this approach, or at least something that could be improved on? Somebody volunteer? Go ahead, and what's the concern with that? So what if you have a whole bunch of controllers? Okay, I mean, I've built applications that managed user profiles, user accounts, blog posts, for example, whatever. You need a controller for each one of those. I don't think you could escape that. So, I'm gonna give other people a chance to talk, but I do get to your point. I wanna clarify it. Go ahead. I was just gonna say repetition. Exactly. Reusability and dependence. Yeah, so quite a bit of that code was repetitive. It was always loading the resource. It's almost the same resource. Actually, if you use one active record, it is the same resource, and then we'd run some validations, and then it would pass to the next controller. So, I mean, there was quite a bit of repetition. We tried to add features a couple of months after a developer had built that wizard on that project, and they wanted us to deliver something in a week, and apparently another developer, before I joined that team, had tried to implement that feature, and it took him a month, and he still couldn't do it with the design they had. It was just taking a long time. Like, he was still not done, and then that guy left. So, I ended up solving the problem with another senior guy, and it was, so I ended up applying this ultra light maintainable wizard approach that I've discovered on the EarlyShares.com project, and it worked out really well. So, which I'll talk about a little later, but that helped us actually develop it in, if I remember right, it was about seven days test first and rewriting the entire thing also. But it was specifically because we didn't have that many controllers anymore. So, we wrote a lot less tests, so we had a lot less code to maintain, so that was part of it. So, another approach I've seen is one controller, sorry, okay, that's just the critique. We already went over that. I don't think I wanna go too much into details for that because we're limited on time. But yeah, let's go next to one action and presenter for wizard step. So, I mean, another approach is, okay, keep one active record, but I've also seen this approach in a code base where there were different, say, new step one, create step one, new step two, create step two. So, there were eight actions on that controller, each mimicking the new and create, say, on a restful resource. So, although it feels restful, it was not rest anymore. It already broke out of the rest paradigm. So, we can improve over that. Also, it still had some repetitive code across the actions. So, I mean, it was just a slight improvement to the problem and not much. Using presenters, which is an abstraction layer between the active record and the controller, is an improvement in the sense that you can put the validations for each presenter per step separately and not have conditional validations. I'll talk more about that going forward. So, it was more something like this, where the controller had a whole bunch of actions that are connecting to presenters and are talking to an active record. Okay, so I already went over the caveats of that. Okay, who here has written a wizard with session accumulation approach? How did that work out for you, or do you think? I'm sorry if it sounds... Well, tell me why you're here. I'm curious. Exactly, yep, yep, yep. So, you end up with a lot of session management code in the controller, which breaks MVC. So, if you're not breaking rest, you break MVC. It's really tough, it's a tough problem. Yes, so, actually, maybe I should have somebody explain that. I saw you raise your hand. Would you mind explaining it to the audience? Right, so, I mean, you go through step one. You gather the basic info from the form. When submitting the form, instead of storing that in an active record, you actually store it in the session, using the session helper in the Rails controller. And then once you move to, and then you redirect to step two, and then you submit that form again, and then you add more stuff to the session. Once you reach the last step, kind of like what you see in this diagram, that's when you're ready to create the active record. So, you pass all of this as the params, and the active record validated, and then you're done. Okay, so, I mean, as far as critique, so reliance on session, storing objects in the session has implications on scalability. Usually, you want to try to store IDs or primitives, because they're easier to move across servers when it's primitive data, and be able to support multiple servers. My understanding is if you have actual objects in the session, it makes it harder for you to scale. Controller code is more complex because of managing session data. Validations could get defined twice, because you might have to validate on every step as well in JavaScript or in some presenter or something, and also presented the last step in the model. So again, I mean, if you're not breaking REST, you're breaking MVC, if you're not breaking MVC, you're breaking duplication, whatever, concerns, so it's a tough problem. Hidden value accumulation, somebody share with us what this is, it's very similar to session accumulation. Yeah, go ahead. I feel like when you submit the form on each step, you're shoving all the values we formed into hidden fields on a page, and then eventually when you submit the final version, that will just set everything to zero once. Yep, so it's not stateful, it's stateless, because it keeps, each request has its state, you don't have to maintain state in a session. So the performance implications are gone, like it has no problems on scalability. But, you might not wanna expose the values all the time on the user page, you can hash them or do encoding on them, so that could improve that problem with regards to keeping form data in hidden fields on the page every step of the way. Yeah, I mean, but the complexity is still there with having to manage the accumulation and having to construct the model at the end. So it's a slight improvement. Where is the state machine for a wizard? Okay, do you mind sharing with us your experience with it? I don't know, I found it, it was better compromise than the other options. Generally it is, one, yeah, so you create one active record, you make the active record a state machine, you have to add a step column on that model to support which step you're on in order for the validation to know which validation is to run for what step. So that way you'll say, okay, on step one, if you have that column, you'll say, okay, you'll have validations that say, okay, if it's step one, then I'm gonna check for first name and last name presence. If it's step two, I'm gonna check that the project details are present, and so on and so forth, depending on what each form, what fields each form contains on the specific step. So yeah, different view per step. I already went over conditional validations to show an example that looks something like that, like validate phone, presence true, if Karen's step is shipping. So that's just one way of doing it. There's other better ways of doing it. There's also gems out there that help you with that, but that's one way of doing it, mm-hmm. Yep, yep, yeah, I'm familiar with that. Yeah, that's why I mentioned there's multiple ways of doing that, that's just one example. Okay, with the other one, you could cheat a bit and set an in-memory variable that represents the step name that you're on and then do that conditional validation that way, whereas with this one, you're only working with one model and you don't, you have it manage the stepping. So yeah, with the other approach, the controller is doing management of the stepping, with this one, the model is doing management of the stepping. So critique. Well, first of all, putting presentation concerns like adding an extra column to represent a state, sorry, a step, is not part of the domain, the business domain, so when you're doing MVC, usually the model, you're trying to put in it as much decoupled logic that's focused on the business at hand as possible in order to be able to maintain that separately from any view concerns or controller concerns. I mean, you can put anything in the model, really, but the reason why we do that is in my experience, when I'm maintaining a code base, if I'm not having to manage view concerns like stepping into a state machine and a model concern, like the business rules of what happens when the project description is not present or whatever, then it's easier for me to maintain that model because I'm not thinking of one thing at a time, I'm not thinking of multiple things at the same time. Also, it makes those models smaller files if you separate those concerns, you don't want a huge model as maintaining a state machine and maintaining business rules and maintaining it like 10 other things. You can manage that with splitting that into modules or concerns, but still, when I'm working with that model, my head will have the context of everything at once, so it wouldn't like, this is more of an advanced programming thing, like once you've been programming for three years at least, or you'll start noticing that, you'll start noticing the subtleties with regards to mixing concerns, like you start understanding why people say, follow the single responsibility principle. I'm not a fan of following it dogmatically, but I think it's a good guideline, like any other guideline, where if you could minimize responsibilities in a model and have it not manage view concerns, then do that, especially if NBC prescribes that as well as that's what all Rails developers on the field would expect. So, I think I pretty much sold that. So, yeah, so I mean, I think that makes it pretty clear why I don't like this approach that much. Also, it's a bit techy, like thinking of a wizard as a state machine is a bit computer science-y, like, I mean, I have a background in computer science, but the point of anything you learn is to apply it in the right place for it, and I don't feel like I'm thinking about a wizard, I'm thinking about the business problem, that's what I really want to think about. I don't want to think about a state machine. As cool as that is, that's not the time to think about it. So, I mean, a thousand one approaches, there's a whole bunch of gems out there. Most of them will simplify the things I mentioned or give you better, shorter DSLs for doing the approaches I mentioned, but none of them achieve all the goals at once of having NBC, REST and all of that. So, I mean, to get back to that, there's REST, NBC, OO, and then the non-functional requirements. So, let's go to jump into this. I think we have about 10 minutes left. So, the first thing, so I'm like, okay, let's try to solve this wizard problem from scratch. Like, as if I just, I'm just gonna get my tools out there, like the object-oriented principles, the domain-driven design principles. Who here has read the book, Domain-Driven Design, or heard of it at least? It's a book that I, my team did a book club on, or a previous team like six years ago, did a book club on, for the sake of learning how to do object-oriented design on real business problems, because a lot of the time you learn object orientation, but it's hard to figure out how to create the right objects for the real, for the right business problem. It's a, that book is a very good book on how to tackle that. So, I started using those tools, like whatever I learned from that book, whatever I learned from object-oriented programming, whatever I learned from like REST, try to figure out what a wizard is. Before I go ahead and talk more of what a wizard is, what do you think a wizard, the wizard's highest goal is? Go ahead. Use of use, review. That's correct. So, you stumped me, because I was gonna ask about the highest goal from the developer's point of view, but you're right, we should think about the user's perspective first. Now, let's dig a level, let's dig, no, that's good, let's dig a level lower. So, okay, so we know that now. That's our guiding principle, it's okay to serve to make things easier for the user, but next, why, okay, technically what, what is a wizard doing? Okay, that's my next question. What is a wizard really doing? Go ahead. That's part of the work, what else? Okay, break down the data. Yep, like separate it. What else? I was gonna say something really similar to that organized the data. Okay, and what's the end goal of running through the entire wizard? Yep, pretty much. So, a wizard is nothing but the good old builder design pattern, anybody's heard of it? I mean, I used to be a hardcore Java geek and design patterns were big in seven days in Ruby, but it's still good to know about things like that, because that pattern flashed in my head right away. I'm like, oh wow, a wizard is nothing but a builder. All it does is like an assembly line of building a car where step one, you know, whatever, you put the chases, second, like step twos, you add more, I don't know, you add the doors, step three, you add the windows, four or five, and then all of a sudden you built a car. So, that's really what it is. Second part of the philosophy that I was following is each step in a wizard is nothing but a partial view of that main, full object you're building. So, one step is about, so say I'm ordering a car and I wanna customize that car, like one step will show me the exterior body, another will show me the interior, to customize the interior with whatever, leather or mahogany, front panel, whatever, and then a third part lets me customize the engine. So, it's just, so all what steps are views. Like, instead of thinking about them as states and as state machine, this is a more higher level way of thinking about it. It's less technical and more, it's just I'm viewing one part of a model. Third part is if you were to, so with that in mind, if you were to think about the rest resources, it's very simple now, it's done. Like, you have the main model, that's the main resource, and then you have the model part, nested model part under the main model, that's the second resource. That's it, you have two restful resources, it's very clean. So, every time you're walking through the steps of a wizard, you're actually editing a model part. So, and so that makes it very, very clear what the rest resources. Another thing in my philosophy about it was I did not want to have conditional validations because they make a model hard to maintain, it's harder to read if statements. Like, if I can have those without if statements, it would be better, especially when you come back and maintain that wizard six months later and then a year later. And on both projects I was on, they actually added steps to the wizard. So, they started with four steps and then they grew to nine steps. And the more I can separate that stuff, the better. And then finally, I just wanted to maintain the views and separate view files as well. I didn't want a single view file that would do it the way I used to write code in ASP where I'd have a crazy if else statement that says if step one showed this part of the form, if step two, show me the project details, if step three, show me a document content upload. I don't want that. That's, yeah, that's ASP programming. Yeah, it'll work. Okay, so really, I mean, a high level is just I have the main model, that's the main resource and then nested under it, there's four different, so what I end up doing is creating four different presenters, one per step, which manages the validations for that step separately, as well as any stepping logic related to what happens when you land on that page, whether the defaults for that form, should we pre-initialize the phone number with zero, zero, zero, zeros, or should we pre-fill the name from the logged in user account? These kinds of concerns now are offloaded cleanly to the model, so we're adhering to MVC. You handle all the wizard intelligence and business logic in the models now, or presenters, I mean, presenter is just another form of a model that focuses on presenting a part of a model. So really, I'm using it in a loose sense. There's many ways to do presenters. I don't care which way. I do have a prescribed way here, but the point, though, first to grasp is that you're operating on a part of the model. You're not operating on the full model, and you're doing the logic in a model, not in a controller. And then the controllers, yeah, so there's two of them. There's the one that manages the main model creation, so the first step of a wizard, when you create it, you create it with the main model controller. So if I have ProjectsController, I'll have a create action. And then that triggers the wizard. It'll create it, and then redirects me to the first step. So when it redirects me to the first step, it takes me to the edit page of model part, the nested model part, with ID step one, for example, or ID basic info. So you use the step names as the IDs of that restful resource, which is perfect rest. Like that goes with a, that gives you an example of why when people talk about rest outside of Rails, they tell you rest does not relate to having a database table. You can, so this is an example where the restful resource is a step. That is like a view of the model, but it's not a resource. Like it's, sorry, it's not a database table. It's not a separate database table. It's just a virtual resource. I need to wrap it up and then I'll take questions. So in a nutshell, you have the model resource nested model. So you end up with URLs like that, which is very restful again, because so you have projects with the ID. We were using friendly ID on that project, so we had a friendly ID, so yeah, say project one, then project parts, and then the name of the step. And there'll be four steps for, every time you run through that wizard, so there'll be four IDs only. It's a finite set. You don't have to store it in the database. So yeah, step name services ID contains validations. Yeah, I already talked about all of that, so let's skip. So the routes are very simple. You just have resources, projects. It's actually only create and show. I left the show out. There should be a show as well, which shows you the landing page of the project when you finish the wizard. And then the project parts, which is edit and update. It's that simple. So here's the project model. It's got the basic definition of that model and associations and so on and so forth. However, what I end up doing is creating a presenter per wizard step. And I nested them under a directory that matches that model name, so project, for example. And that's one way of doing it. There's many ways of doing it, but in this case, only step one and two had customizations over that model. Step three did not have validations of its own, so I didn't even have to create a file for it. And step four actually didn't have anything either, so I didn't have to create a file for it. But yeah, step one, you can't see the details, but the point is that these are validations for step one only. So that's the first and then the first step, as well as some business logic related to it, like initializing default, like initialize from user, which initializes it from the signed in user. And then there is the project detail model, which has a few validations only, only three because it only has three fields on it. So that one is also cleanly separated, nice, easy to maintain. So you go back to maintain the code and it's like, it's like, it's quicky clean. It's so easy, it's like the way programming should be. So the project's controller, the create action, all it does is it creates the project and then redirects to the edit page of the first step, which is basic info. And then the project parts controller has the edit and the update and all it does is it steps through the wizard. Now, what I ended up doing here was the way I reasoned about it is that the project in MVC or sorry, project controller in MVC is a project parts controller is actually, although it's a controller, it's a model of sorts. It's a model focused on control flow and stepping through a wizard is control flow. So then I made the controller responsible for it. So here at the top, it defines the order of the steps and then based on that, it walks through them. So in a way, the controller is the state machine except I didn't need a state machine because I'm not maintaining state. I do it with, I don't need to maintain state. Every time we finish a step, we can redirect the next step and I can pass the ID of the next step restfully and redirect to the next resource. So I don't need to maintain what step am I on. It's always on each page. Like on each page I can know, if I'm on step two, I know that if I hit submit, it'll take me to step three next. I don't have to, like I just know that from this array that we define over here, which orders the steps in the wizard. So yeah, we're almost done. But yeah, I mean, it's got stepping logic. There's a gem out there called Wicked that helps you implement this sort of logic in a controller. I'm starting a gem called ultra light wizard that will do similar stuff except it'll add the concept of presenters to it as well. But until then you could use Wicked for the controller part. The views, you'll just have a different wizard part view, or sorry, step view. And one thing I didn't show is that this edit action, it actually, when it renders the view, you don't render an edit.html.erb, you polymorphically just render the step name and then it ends up picking a view matching that step name. So if I render basic info, it renders basic info.html.erb, which contains the edit form for that step. If I render the detail step, then it renders detail.html.erb. So that's why I have these. So that way we have the views separated as well so we achieve that goal. The form that you put can be the same on all views actually, because you're editing the same model. You're just editing different parts of that model but it's the same model on all pages. So the root model is project, but on one page I'm editing nested documents, content uploaded documents. On another page I'm editing just the first name and last name, so on and so forth. So you could actually wrap this whole thing up in the helper, call it project form port, that's super short, just use that. I know in one of my projects, another senior developer on the team did that, like he did that as a refactoring step. So I mean this is an example of a view, it's very just straightforward view form, like straight rails, nothing special about it. So that concludes the talk. So I mean I talked about why use a wizard, provide a wizard example, implementation goals, other implementations out there and finally talked about what's my recommended approach for sticking with REST, MVCOO, and all the things that would help ensure that your code is not maintainable only today but also a year from now and also by other developers that would join the team with minimal training efforts hopefully. This is the project that I launched and it's empty right now, but I would like to, but I mean you can star it and monitor it and hopefully I have something out soon. Maybe I'll do it at RailsConf, somebody wanna pair with me on this, you're welcome to. So yeah, my name is Andy Malay, I'm the VP of Engineering at a remote only or 100% remote option consulting company called Big Astronaut. These are my folks over here, Lance, CTO. Oh yeah, she's fun officer as well. We got T-shirts and stickers and we're hiring. Sweet, yeah, thank you everybody.