Concerns, Decorators, Presenters, Service Objects, Helpers, Help me Decide! RailsConf 2014 Chicago April 22, 2014 ! Justin Gordon @railsonmaui Rails Consultant www.railsonmaui.com 2
DHH Quote JG: "This is starting to boil down to utilize the framework capabilities and move beyond only when necessary.”
DHH: "Which is really just an extension of KISS (Keep It Simple, Stupid). When you use the framework code for what it’s intended, you’re not cutting against the grain. You don’t need to write as much code. It’s clearer to everyone because it’s the same approach everyone else is taking." 11
Scenario • You’ve got duplicated code in two models, different database tables.
• Tease out a concern that applies to both models. Since your models extend ActiveRecord::Base, using regular inheritance is problematic. Instead, use a concern. 16
Rails Concerns 18 Big Model some-domain class macros some-domain instance methods some-domain class methods other class macros other instance methods other class methods Domain Concern some-domain class macros some-domain instance methods some-domain class methods
DHH on Domain vs. Technical Refactoring "I’ve not yet found a case where the scope of the current file/ class couldn’t be brought under control by using a domain-driven extraction approach."
"In a sea of 60 methods, there will always be domain-based groupings, rather than technical groupings. Never seen that not be the case." 20
Scenario • You have duplicated rendering code in several files.
• Remedy:
1. If rendering code, use a partial.
2. If ruby code, use either a view helper or create a static method on a utility class. View helpers have access other helpers. Utility classes require extra work to call view context methods. 31
Presenters 35 Presenter Object Wrapping Data Needed by View Smaller Controller Action Creating Only the Presenter Instance Big Controller Action Setting Many Instance Variables View with ONE Instance Variable View with MANY Instance Variables before after
Scenario • Problem: A controller file is huge with many actions and many more private methods.
• Solution:
1. Split up the controller into multiple files by having your routing file map to different controllers.
2. Put any common functionality in a controller concern, similar to how you would do it for a model. An alternative is having an inheritance hierarchy of controllers. Mix-ins are more flexible. 36
• Your Presenter class needs to access the view context, but it’s PORO.
• Solution:
1. Use this include in your PORO: “include Draper::ViewHelpers”.
2. Pass the controller instance into the constructor of the Presenter (include required helpers in controller), or set the view context in the view file.
3. Pass the view context into the methods that need it on the Presenter. 37
–David Heinemeier Hansson “I've yet to see a compelling "make action a service object" example in the wild. Maybe they exist somewhere, though. Then again, maybe unicorns are real too.” 40 https:/ /gist.github.com/dhh/10022098 Service Objects?
Service Objects Example 41 Big Micropost Create Action on Controller MicropostCreationService ControllerResponse Flash, Flash-now, status code Tiny Micropost Create Action on Controller https://github.com/justin808/fat-code-refactoring-techniques/pull/6 before after
A Bit Humbling… DHH: "Sorry to keep shooting the patterns down, but this is exactly what I mean when I say that most code does not need patterns, it just needs to be rewritten better."
JG: "I think it's a pattern either way. The pattern you presented is to use validators rather than a separate object."
DHH: Right, which Rails already has built in, and the code is easier to follow with less work. 42
Single Purpose Controller • Controller with only one action
• https://github.com/justin808/fat-code-refactoring-techniques/ pull/7 43 Big Micropost Create Action on Controller Micropost Controller Just for Create Rest of the Micropost Controller
DHH on Controllers “It’s [controller] intended to process the incoming request, fetch the model, and direct the user to a view or another action. If you’re yanking logic of that nature out of the controller, you’re making an anemic controller. Shoving this into a service object is imo the lazy approach that doesn’t deliver any benefits in terms of simpler code. It imo is the sweep-it-under-the-rug approach. 44
DHH on the work of a Controller "I’ve yet to see compelling controller code that couldn’t be slimmed down by simply writing it better, spinning off another controller, or moving domain logic to the model. Here’s another example of a code ping pong I did off a convoluted action in RedMine: https://gist.github.com/dhh/10023987” 45
Thanks! Special thanks to those that helped review my code samples to this talk: @dhh, @jeg2, @gylaz, @jodosha, @dreamr, @thatrubylove, @therealadam, @robzolkos, Thoughtbot’s Learn program forum and Ruby Rogues Parley Forum 52 Rails on Maui HQ, aka Sugar Ranch Maui