1
May 11, 2015
1
Concerns, Decorators, Presenters, Service
Objects, Helpers, Help me Decide!
RailsConf 2014
Chicago
April 22, 2014
Justin Gordon
@railsonmaui
Rails Consultant
www.railsonmaui.com
2
3
ControllerModel &
Avoid the
Ball of Mud
May seem fun…
Guaranteed:
the perpetrator is
not doing the
cleanup!
4
6
7
How do we organize the mess?
Organizational Conventions Matter
8
Department Store
Organizational Conventions Matter
9
Thrift Store
Like Fashion…
Coding Style ➜ Personal Preference
10
And Our Style is…
11
DHH QuoteJG: "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."12
Microposts Example
13
Micropost Model
User Model
Micropost Controller
User Controller
1
N
Refactoring Examples in Pull Requests
• https://github.com/justin808/fat-code-refactoring-
techniques/pulls
• Based on Michael Hartl’s “Rails Tutorial” MicroBlog
example application
14
Objectives
Patterns &
Techniques
15
DRY
Methods
< 5 Lines
Classes
< 100 lines
One Instance
Variable in
View
Easy to
Test
ConcernsDraper
Decorators
Validation
Classes
Presenters
Split-up
Controllers
ClarityEasy to
Change
Guidelines
Move Logic
to Models
Easy to
Find
• Huge model file with even larger spec file.
• Break up the model/spec using Rails concerns. Try to
break it up by domain, but any logical split will help.
16
Scenario
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.
17
Rails Concerns
18
Big Model
class macros (has_many, validates, etc.)
instance methods
class methods
Rails Concerns
19
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 macrossome-domain
instance methodssome-domain
class methods
Concerns: How
• Discover set of related code for a problem domain
• Create a module with extends ActiveSupport::Concern
• Move code into the Concern
• Break out tests into corresponding test file for the Concern
20
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."
21
Concerns: Example
• Break out Emailable Concern out of User model
• Captures domain logic of lower case emails on user
model
• Benefits: Smaller model, smaller spec
22
Objectives
Patterns &
Techniques
23
DRY
Methods
< 5 Lines
Classes
< 100 lines
One Instance
Variable in
View
Easy to
Test
ConcernsDraper
Decorators
Validation
Classes
Presenters
Split-up
Controllers
ClarityEasy to
Change
Guidelines
Move Logic
to Models
Easy to
Find
Scenario
• Model file creating detailed validation messages with
HTML tags and URL links.
• Move the message creation code into a Draper Decorator
for the model. These decorators work great for model
based presentation code.
24
Draper Decorators
25
Mode and
Model-
Concerns
Presentation
Code (views,
helpers)
Draper Decorators
26
Mode and
Model-
Concerns
Presentation
Code (views,
helpers)
Draper
Decorators
Draper Decorators: What?
• Popular gem that facilitates model decorators
• Very simple, easy to use
27
Draper Decorators: Why?• Removing presentation code from your model or model-
concerns
• Consolidating some helper, view, controller methods by
models
• Presentation code relating to one model, but multiple
controllers/views
• Consolidation of flash messages related to a given model
28
Draper Decorators: Why• Decorators are the ideal place to:
• format complex data for user display
• define commonly-used representations of an object, like a
name method that combines first_name and last_name
attributes
• mark up attributes with a little semantic HTML, like
turning a url field into a hyperlink
29
Draper Decorators: Alternatives
• View Helpers
• PORO, getting a handle to the controller or view
30
Example
Several views have code that format the
micropost.created_at:
Posted <%= time_ago_in_words(micropost.created_at) %> ago.
31
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.
32
Objectives
Patterns &
Techniques
33
DRY
Methods
< 5 Lines
Classes
< 100 lines
One Instance
Variable in
View
Easy to
Test
ConcernsDraper
Decorators
Validation
Classes
Presenters
Split-up
Controllers
ClarityEasy to
Change
Guidelines
Move Logic
to Models
Easy to
Find
Scenario
• You are setting too many instance variables in the
controller action. You also have local variables being
assigned in the view.
• Presenter pattern: Create a PORO that wraps up the
values and logic going from the controller to the view.
34
Scenario
• Fragment caching in your view, but some extra queries
still run
• Use the Presenter pattern, with memoization in the
instance methods.
• @foobar ||= calculate_foobar
35
Presenters
36
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
beforeafter
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.
37
Scenario• Problem:
• 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.
38
Objectives
Patterns &
Techniques
39
DRY
Methods
< 5 Lines
Classes
< 100 lines
One Instance
Variable in
View
Easy to
Test
ConcernsDraper
Decorators
Validation
Classes
Presenters
Split-up
Controllers
ClarityEasy to
Change
Guidelines
Move Logic
to Models
Easy to
Find
40
If a minor posts profane words:
1. The post shall not be valid.
2. A counter will track how many times the
minor tried to use profanity.
3. The minor's parents shall be notified.
4. A special flash alert will alert the minor
to profanity usage.
Business Case
–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.”
41
https://gist.github.com/dhh/10022098
Service Objects?
Service Objects Example
42
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.43
Single Purpose Controller
• Controller with only one action
• https://github.com/justin808/fat-code-refactoring-
techniques/pull/7
44
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.45
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”
46
Plain Rails
47
Big Micropost
Create Action
on Controller
Micropost Model
User ModelSmall
Micropost
Create Action
on Controllerbefore
after
Scenario
• Excessive model logic in complicated controller method.
• Either:
• Move model logic out of controller and into the models,
utilizing Rails features such as validation.
• Create a non-AR based model to handle an interaction
between two models (aka “Service Object”)
48
POR (Plain Old Rails)• Use Rails Models, Validation, and Controller for their
proper jobs
• KISS (Keep It Simple Stupid)
• Don’t Invent Patterns That Don’t Need to be Invented
• Know the why of the Rails way
• Know the Rails way before deviating
49
Refactoring Steps
• Move validation code and checks out of controller to
model
• Move creation of flash message to decorator
• Move validation code to validation class
50
References
• Rails Guides: http://guides.rubyonrails.org/
• Patterns to Refactor Fat ActiveRecord Models:
http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-
decompose-fat-activerecord-models/
• DHH’s Example of 2 Controllers with Concerns:
https://gist.github.com/dhh/10022098
51
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
Thanks!• More details at my blog:
http://www.railsonmaui.com
• Feel free to contact me
regarding your projects
• http://airpair.me/railsonmaui
53