RailsConf 2014

Concerns, Decorators, Presenters, Service Objects, Helpers, Help Me Decide!

Concerns, Decorators, Presenters, Service Objects, Helpers, Help Me Decide!

by Justin Gordon

In this video titled "Concerns, Decorators, Presenters, Service Objects, Helpers, Help Me Decide!" presented by Justin Gordon at RailsConf 2014, the speaker discusses methods for refining and optimizing code in Ruby on Rails applications. The goal is to help developers understand how to make their code more maintainable, DRY (Don't Repeat Yourself), and easier to test by applying various design patterns and coding practices.

Key Points Discussed:
- Understanding the Problem: Justin starts by likening tangled code in Rails projects to a 'ball of mud,' illustrating how unclean code accumulates over time due to poor organization. He emphasizes the importance of understanding where to add new lines of code meaningfully.
- Techniques and Patterns: The core of the talk revolves around several key patterns including:
- Concerns: Used for extracting related behavior from classes to keep models cleaner and focused on a single responsibility.
- Draper Decorators: These are designed to handle view-specific logic that would otherwise bloat models, allowing for the separation of concerns between business logic and presentation.
- Presenters: These help manage instance variables in views, making them cleaner and providing better organization of data for the view layer without cluttering controllers.
- Service Objects: Although initially included in the talk, Justin ultimately concluded that service objects might not be necessary in many cases, advocating for a clearer understanding of where to place logic in Rails, favoring models over standalone service objects.
- Code Examples: Throughout the presentation, Justin provides live coding examples to demonstrate how to apply these techniques effectively. He refers to specific patterns such as the concerns pattern and the use of the Draper gem to create decorators that simplify view logic.
- Refactoring Philosophy: A significant takeaway is the ethos of using Rails effectively and knowing when to apply certain patterns versus simply avoiding over-engineering solutions. Justin shares his learning experiences from interaction with other developers and emphasizes that sometimes cleaner solutions can be achieved without creating new patterns unnecessarily.

Conclusion:
The main takeaway from this talk is the importance of maintaining clarity and simplicity in Rails applications. Justin encourages developers to leverage Rails' built-in features properly, understand the framework’s capabilities, and focus on making their code simpler and more effective, rather than complicating it with unnecessary abstractions.

00:00:16.200 Is there, um, you guys hear me okay? Okay, I didn't realize, um, that no one was giving me the clue that we were starting.
00:00:22.840 So, I have 38 minutes to go. Hi, I'm Justin Gordon. I flew out here from Maui, and my handle on Twitter is @railsonmaui.
00:00:31.030 I'm a Rails freelancer. I'm a poster child for remote work, the guys at 37signals do that all the time—working on Google Hangouts and everything.
00:00:43.510 Anyway, um, I am so stoked to be here. I'm thrilled. My talk today is called 'Concerns, Decorators, Presenters, Service Objects, Helpers, Help Me Decide!'
00:00:56.290 Well, after I saw David's talk this morning, I realized that wasn't a very good title! Okay, here's my new title:
00:01:03.460 'How to Lose Model and Controller Fat Fast for Men and Women.'
00:01:21.520 The ball of mud! How many of you have ever experienced this in a Rails project?
00:01:27.460 Well, look, I only learned Rails three years ago, and I understand how this goes. It can be a little mysterious when you first start programming in Ruby.
00:01:45.520 You know, the natural place to put another line of code is in the same place next to where you need to change something, so it just kind of keeps growing and growing.
00:02:05.350 Anyway, I love this analogy of the ball of mud. Any of you have kids? I have a wonderful five-year-old, almost six.
00:02:10.750 They don't like to clean themselves, so you try not to get them too dirty, but they like to play.
00:02:18.060 I guarantee you, this guy is not cleaning up his mess. You can have the same analogy with some software projects.
00:02:26.590 When I first wanted to develop this talk, I wanted to make it so that I was going to get some real code. So, I started asking around in the forums. By the way, big shout out to the Parley forum and Thoughtbot's forums—they're awesome! I'm on there all the time.
00:02:58.080 So, how many of you have heard about the Sandy Metz role? I've been following that stuff a lot recently.
00:03:06.840 That's kind of what got me going for this talk. I wanted to bring up some real code here. So, we've got some code from Redmine.
00:03:18.060 We have a user model with 706 lines, and we have the account controller and the lost password method with 52 lines and four levels of nesting. Yeah, that's not really that much fun to work on.
00:03:37.870 So how do we improve this? I came up with some examples that are much simpler. I realized it was going to be too hard, so my examples are built on top of Michael Hartl's Rails tutorial.
00:03:45.070 Actually, I want to take a second to tell you a bit about how I developed this talk because I think it's really relevant. First of all, I wasn't planning on giving a talk. I was brought here by JetBrains and RubyMine.
00:04:02.770 They said, 'Hey, could you give a Rails talk for us?' I had just been going over a lot of material.
00:04:10.000 So then I see a little Twitter thing: okay, so the last day for conference proposals.
00:04:19.930 I've been working on applying these patterns to a big project I've been doing, and this is the concept of this talk.
00:04:32.329 So, we're going to try to fix how we organized this mess. One of my examples is like a clothing store—you go to a clothing store and, how do they organize the clothing? By color.
00:04:44.870 Everything that’s black is in one place; that would be a disaster!
00:04:53.480 You’d have women's clothes, baby clothes, everything, and maybe you’re looking for something for men’s clothes.
00:05:05.160 Among different kinds of clothing stores, those organizational patterns were different, and you can use this analogy.
00:05:15.280 If you were doing Struts versus if you’re doing Rails, it’s going to look totally different.
00:05:34.010 Look at fashion: people’s ideas of what good style is will be different.
00:05:47.230 We saw this morning that David's talk was clearly on the same campus as some other people about what this is.
00:05:56.639 So, what's our style? Our style is Rails.
00:06:09.000 That is going to be one of the primary topics about what the talk is, because it is not going to be about some computer science 'this is the best way to abstract everything out.'
00:06:28.780 The thing I love about Rails and manage is how amazingly productive the whole ecosystem is, and part of that is that you can go online and find the answer to just about any question on Rails so easily.
00:06:44.190 So a big part of what this talk is about is how the patterns evaluate: does this pattern fit into Rails as it is now?
00:07:07.680 And a big part of that is who we saw speak earlier—David.
00:07:24.790 So, lo and behold, I put my code up on GitHub, and this is how I developed my talk.
00:07:31.920 All this code is on GitHub, so you can go there and see how it was developed.
00:07:41.250 I also put in a pull request for each of the patterns I’m going to show you.
00:07:55.080 I just wanted to test this out. It was too painful for you to watch me kind of fat-finger things.
00:08:02.200 I’ve got too much material to cover, so I’ll show my code samples at the end.
00:08:15.820 But it's all there on GitHub, and even better than just having code samples, there's review content and comments from guys like James Edward Gray.
00:08:28.540 I had a chat with David before I emailed him, and lo and behold, he sent me back a response almost immediately, starting to give me code reviews!
00:08:43.440 Wow, this is kind of cool! Having a code review from someone as esteemed as David was very humbling for me.
00:09:02.740 So, we’re going to see the benefits of this interaction. This talk was basically developed in an open-source way, which is something that didn’t happen five or ten years ago.
00:09:20.849 Okay, so here’s a summary of where we’re going in this talk.
00:09:37.120 These are exact quotes from David: 'This is starting to boil down to the framework capabilities and moving beyond only when it's necessary.
00:09:49.610 When you really use framework code for what it’s intended, you’re not cutting against the grain, and that's the key point.'
00:10:01.260 So that's what we’re going to be showing you stuff with that in mind.
00:10:12.630 The example I'm going to give you, just to give you context later on, is a micro post example.
00:10:28.410 And let me show you just really quickly; you know what? I'm going to show the example at the end because somebody doesn't want me to quit out of presentation mode.
00:10:44.600 Okay, so when I gave this talk before, one of the things someone got confused about was they didn’t understand what the examples were about.
00:11:01.000 So this is a little like a microblogging application, like Twitter. I'll show you later on how I would extend it for little kids, who should not be using profanity or the word 'poop'.
00:11:15.730 So everything's there. This is the structure of the talk and where we’re going to go.
00:11:27.000 So when we’re evaluating code, we want to discuss how we’re going to say what’s better or what’s not.
00:11:38.220 Before we even discuss if it’s better or not, let's agree on the guidelines.
00:11:49.810 So I’m giving you my guidelines for evaluating stuff and the objectives, and then we’ll go over the patterns and techniques.
00:12:06.630 So, small methods: less than five lines. We no longer care about how long it takes to invoke a method in Ruby.
00:12:18.220 Small classes—that's huge, and I’d extend that to even small files.
00:12:36.150 The reason why is that who wants to scroll through a thousand-line file looking for something? I much rather have stuff broken up into manageable, easy-to-use chunks.
00:12:50.760 One instance variable per view: there’s another thing to consider. It's a pain if you have ten different variables being set up in all different places.
00:13:06.030 So that makes accessing it easier. Objectives: DRY (don’t repeat yourself). Everyone should know what that is.
00:13:20.010 Easy to test and, after this morning's talk, we discussed relative testing ease—clarity, easy to read, easy to find, and easy to change.
00:13:32.540 Now let's talk about the patterns. The pattern I want to address includes concerns,
00:13:46.010 Draper decorators. I use the term Draper decorators, not just decorators, because I'm specifically referring to using the Draper gem, which I like.
00:14:02.620 Presenters: the next term, has lots of meanings. For presenters, that’s going to be my own little pattern.
00:14:17.451 Then we'll discuss how to split up controllers, which I'm not going to talk about, as it's in the pull requests, and moving logic to models and validation classes, which will also be in the pull requests.
00:14:28.170 I’m missing one of the main topics here that you probably all came to hear about: service objects.
00:14:50.420 Well, okay, you’ll see shortly—service objects were originally part of it, but after discussing with David, they’ll not be featured in this talk.
00:15:04.280 The pull request is still there and you can still see the code originally developed and the responses.
00:15:14.230 So first scenario: you’ve got a huge model with a large spec, so you want to break up the model spec. How are you going to do this?
00:15:30.410 We're going to use concerns and specifically, we’ll try to break it up using domains. I’ll tell you what that means in a second.
00:15:50.680 I'll tell you what I first started using concerns: a real big model file. I initially thought I’d just chunk it and start.
00:16:03.179 I saw the example that David originally gave: taggable. I had pages and pages of finder methods.
00:16:18.680 So I didn't want to have my queries scattered all over my controller, so I chunked that into a concern I called finder methods.
00:16:33.180 Then I created one for validation methods. Hey, David totally hated that! He said we're going to break stuff up by domains.
00:16:46.110 So here’s a visual representation of how you use concerns. Say you have a class—a big file.
00:17:03.780 Yes, you need to understand your class macros or things like 'belongs', 'has many', 'validates', etc.
00:17:15.810 You have your instance methods and class methods. What you want to do is break this stuff out by domains.
00:17:35.579 You literally cut and paste it. This is the easiest refactoring. If your tests were good, you don't need to add any more tests.
00:17:57.230 So when I get to the code examples, I'll give you a quick rundown. How do you discover the domain? You create a concern module.
00:18:11.940 Then you're going to move code into the concern and break out tests.
00:18:23.000 One of the things I really liked was when I had a big user model and an even bigger user spec test.
00:18:37.460 I had my finder methods, then I had my user directory, and then I had my finder method spec—so nice chunks!
00:18:50.070 Okay, so this is a direct quote from David: 'In a sea of 60 methods, there will always be domain-based groupings rather than technical groupings.'
00:19:04.130 Never seen that not to be the case. At first, I didn’t get the whole thing with domain-based groupings.
00:19:18.990 The example I'm going to show you is called Email Abul, which has some properties—like the emails are always stored in lowercase.
00:19:31.450 When I search for email, I always want to convert the search query to lowercase. That makes a nice little domain.
00:19:45.070 Just like his example of taggable.
00:20:00.280 The next one I’ll be going through in the code is Draper decorators.
00:20:13.780 So I get a good example for this: you have a model file creating detailed validation messages with HTML and URL links.
00:20:31.300 How many people ever end up in a situation where you need a validation message and you want to display a URL through which someone can click to fix the problem?
00:20:47.489 Or you want to put some words in bold? This is view-based, presentation-based logic. But you’re supposed to be creating that in the model!
00:21:01.890 Then what happens is you end up needing to call something like the URL helpers from your model.
00:21:13.180 Now, for a beginner Rails programmer, how confusing is that? Very confusing! Because 'whoa, I was doing that in my views and my controllers without any problem.'
00:21:28.590 Basically what the Draper decorators will allow you to do is move this logic into a decorator class.
00:21:42.410 This makes it easier; it’s basically like imagine concerns but going the next step for your presentation stuff.
00:21:55.230 So, here’s what you’ve got: you’ve got the presentation code, you’ve got the model, and the next thing we’re going to do with the decorators is break it out.
00:22:08.320 The decorators will cover the overlap between the model and the presentation layer.
00:22:20.130 So, it’s like a concern but for just your presentation stuff—which makes it easier to do with the Draper decorator gem.
00:22:33.840 Alternatives to using the Draper decorator: if you’re doing this, you can make view helpers, you can just make plain old Ruby objects and pass context into them.
00:22:50.950 I'm going to give a piece of duplicate code, and I’ll show you this once I get to the code.
00:23:04.370 Presenters—this is a pattern that can be difficult to come up with a name for.
00:23:16.690 This is because 'presenter' and 'decorator' are confusing; they have different meanings in like the Gang of Four books, etc.
00:23:31.430 They just mean different things to different people. I asked around, and David gave me a thumbs up on the word 'presenter' for this pattern.
00:23:44.840 So, what’s the scenario? You have too many instance variables and a controller action, and you’ve got all these instance variables accessed in your view.
00:23:59.040 You might have a bunch of logic in the view code. It’s better not to have too much code in your Ruby code, in your view code.
00:24:14.520 What we’re going to do is create a plain old Ruby object.
00:24:29.230 We’re going to pass the stuff we need into this Ruby object, and that Ruby object is going to be accessed from the code.
00:24:42.840 I’m going to show you this pattern a little later, but just imagine all those declarations for those instance variables.
00:24:57.540 We’ll put whatever information we need to create those in the parameters for initializing this new object.
00:25:11.250 This is why I’m calling it the presenter, and then your view code will access it through presenter.micro_post.presenter.
00:25:27.470 Another great example of how these presenters really help out is with fragment caching.
00:25:43.090 Fragment caching is awesome! If you haven’t tried it, one of the things that happens when you use fragment caching is you look at your logs.
00:25:56.780 You look at your logs to see if this query ran, and maybe you have a slow query.
00:26:12.390 Before, if you were declaring these instance variables in your controller, that action would still run the query.
00:26:28.970 However, when you get to your view and find that the cache block didn’t run, you still ran the query.
00:26:44.090 By putting the logic in this presenter object, what happens is you access that value from the view code, making it easier to memoize.
00:27:00.060 So, it ends up looking like this: you have stuff in there like a method declaration for foo_bar, where it equals calculate_foo_bar.
00:27:14.080 This gets called from the view so it works perfectly with caching.
00:27:30.190 So, what it looks like is you’ve got a big controller action with many instance variables, and then you have a view with many instance variables.
00:27:44.120 What we’re going to do is make a smaller controller action, and only initialize the presenter instance.
00:27:51.760 Then, what’s going to happen is the view will use that one instance variable and call into the presenter object.
00:28:01.530 The next topic is about moving logic to models and the validation classes.
00:28:19.169 Okay, so let me tell you what the example is. I’ve actually got this running, and all you need to do is simple extension steps.
00:28:37.370 The micro-blogging example app is built on a 'kiddies safe login' business case.
00:28:54.580 What happens when a kid says 'Dad is a poopface and fartface' is that your parents will be notified.
00:29:10.530 It’s going to say, 'You better watch your language!' and you've used profanity 50 times.
00:29:28.340 We access the user model there too, and you're thinking how we're going to write the code for this.
00:29:44.279 I'll get into this in just a bit, but you can write it out in the controller, it makes sense, especially when you need specialized flash messages.
00:29:57.000 However, if you create a service object, then, okay, that’s the business case.
00:30:13.450 I’ve yet to see a compelling 'make action' service object example in the world. Maybe they exist somewhere, but then again, maybe unicorns are real.
00:30:29.180 By the way, my examples got reviewed during that context.
00:30:40.930 So, what is the service object example? We’re going to take the big micro post create action on the controller, and I’ll be showing you that in the code.
00:30:55.560 So, here’s the before and the after. I thought this was the coolest thing ever when I was originally getting started.
00:31:10.290 We have a tiny micro post create action. In the action, we create the service object, the micro post create service object.
00:31:27.890 What that does is everything it needs to do, and it's in one nice little small object that fits the Sandy Metz rules.
00:31:43.280 It's also easy to test! I can just test the stuff directly, but I need to make sure that the flash message is correct in different circumstances.
00:31:58.510 Now, controller specs can be tricky, so that worked out really nicely.
00:32:15.780 The code has to do is you send back all the stuff to the controller: your response code, your flash, and whatever just needs to be returned.
00:32:35.080 What I found while doing my own project was that I was sending that stuff back all the time.
00:32:50.330 I called it the controller response; I thought, 'Cool, I’m going to present this at RailsConf. This is going to be great!'
00:33:05.050 Let me tell you, there’s nothing like putting your code out there for review to the entire open-source world.
00:33:20.920 Getting people like David and James to review your code is a great learning experience.
00:33:39.010 So that was the example I originally came up with for the service object.
00:33:52.150 This was David's response—I started to keep shooting patterns down, but this is exactly what I mean.
00:34:07.420 Most code does not need patterns; it just needs to be rewritten better.
00:34:23.170 He was telling me to put it in the validator.
00:34:35.049 I told him I think that's a pattern out of the way the pattern you presented, which is validators rather than a separate object.
00:34:48.990 He said that’s what Rails has built in.
00:35:02.740 Whether you call them patterns or techniques, the objective is clear code.
00:35:18.970 I thought about that, so that was the criticism!
00:35:36.090 I had too much code that was controller-specific in my service object.
00:35:54.240 I bet some of you have made this same mistake.
00:36:07.230 So then I thought: 'Okay, so what should I do?' I saw one of David’s examples.
00:36:22.990 Instead of having one controller class per model, why not have just one controller class for a single action?
00:36:34.670 That way, all the private methods and everything just concern that one action, bundling up the logic nicely.
00:36:59.560 This is going to be great! So I go and show it to David. He didn’t like that at all.
00:37:11.800 I still left the business logic of checking for profanity and stuff in the controller.
00:37:35.870 What he really wanted was to move as much stuff over to the models.
00:37:52.920 This was his response to that pattern: shoving this into a service object is, in my opinion, a lazy approach.
00:38:09.570 You might think it delivers no benefits and results in simpler code.
00:38:23.010 But it’s a sweep-under-the-rug approach.
00:38:34.950 The solution was plain Rails. Basically, just have the controller put as much stuff in the model.
00:38:54.010 That includes calling out the models for what they need to do.
00:39:05.400 So here's the scenario: when you have excessive logic, here are the things you might do.
00:39:22.740 You can move the logic out of the controller into the models using Rails validations.
00:39:38.320 Or you can create a service object to handle interactions between two model objects.
00:39:49.560 What people have been referring to as a service object.
00:40:01.450 Feel free to create more models but don’t substitute the stuff that should be in the controller into service objects.
00:40:12.250 Okay, let's take a look at some code.
00:40:21.840 I'm hoping we’ll have a little bit of time for questions at the end.
00:40:37.540 So, once again, this is all on GitHub.
00:40:48.360 Here’s the example we’re going through: 'Dad is wonderful! The post says: Dad is poop.'
00:41:01.240 Definitely have fun with it over on GitHub and take a look at that stuff!
00:41:13.040 Okay, I’m going to go through the examples again to reiterate what we got here.
00:41:25.950 So here’s the patterns: the concerns pattern.
00:41:38.620 What we do is take several different bits of code that all concern emails and separate that information out.
00:41:52.870 What we do is take the email domain checking, and you may validate email addresses.
00:42:05.950 We will create a module, in this case, called 'Email Abul', and we’re going to extend ActiveSupport::Concern.
00:42:17.150 Next, we’ll use the 'included' block and have all of your class macros there.
00:42:28.460 This leads to easier testing as well.
00:42:41.590 That’s the benefit of using these concerns as smaller specs.
00:42:58.370 Next, I’m going to show you a Draper decorator.
00:43:06.770 With Draper decorators, it’s super simple. To implement it, all you need to do is add the gem, 'Draper'.
00:43:19.820 Here’s what the decorator looks like. I cleaned up some repeated code in my views.
00:43:31.800 If you're trying to call a method that requires a helper with a model, that gets tricky.
00:43:50.650 Draper makes it easy to manage and structure code.
00:44:01.920 Using your decorator object to wrap all that logic up adds clarity.
00:44:14.730 I’ll show you how it gets instantiated shortly.
00:44:26.710 You use decorators that will delegate properties to the models.
00:44:38.830 Instead of creating a fatter model, you ensure a clearer structure.
00:44:52.330 I’ll also point out that presenters can help manage complexity.
00:45:06.070 There you go; just keep creating a structure that makes sense.
00:45:20.190 So, let’s say you have instances of merging, along with how you initialize.
00:45:36.130 You can set your entire scenario with one single class.
00:45:46.760 And once you create a few of these, you'll find you can simplify with search and replaces.
00:46:01.990 Continue to evaluate your designs, and always keep things as simple as possible.
00:46:13.150 Make sure to validate your experiences and growth as you learn!
00:46:27.190 Does anybody have any questions? Thank you all for coming!