$30 off During Our Annual Pro Sale. View Details »

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

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

Techniques to cut fat from models and controllers. Code available here: https://github.com/justin808/fat-code-refactoring-techniques

Justin Gordon

April 22, 2014
Tweet

Other Decks in Programming

Transcript

  1. R A I L S O N M A U I
    1

    View Slide

  2. Concerns, Decorators, Presenters, Service
    Objects, Helpers, Help me Decide!
    RailsConf 2014
    Chicago
    April 22, 2014
    !
    Justin Gordon
    @railsonmaui
    Rails Consultant
    www.railsonmaui.com
    2

    View Slide

  3. 3
    Controller
    Model &

    View Slide

  4. Avoid the
    Ball of Mud
    May seem fun…

    !
    Guaranteed:

    the perpetrator is

    not doing the cleanup!

    4

    View Slide

  5. 5
    Sandi Rules
    method > 5 lines
    class > 100 lines

    View Slide

  6. 6
    How do we organize the mess?

    View Slide

  7. Organizational Conventions Matter
    7
    Department Store

    View Slide

  8. Organizational Conventions Matter
    8
    Thrift Store

    View Slide

  9. Like Fashion…
    Coding Style ➜ Personal Preference
    9

    View Slide

  10. And Our Style is…
    10

    View Slide

  11. 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

    View Slide

  12. Microposts Example
    12
    Micropost Model
    User Model
    Micropost Controller
    User Controller
    1
    N

    View Slide

  13. Refactoring Examples in Pull Requests
    • https://github.com/justin808/fat-code-refactoring-techniques/
    pulls

    • Based on Michael Hartl’s “Rails Tutorial” MicroBlog example
    application
    13

    View Slide

  14. Objectives
    Patterns &
    Techniques
    14
    DRY
    Methods
    < 5 Lines
    Classes
    < 100 lines
    One Instance
    Variable in View
    Easy to
    Test
    Concerns
    Draper
    Decorators
    Validation
    Classes
    Presenters
    Split-up
    Controllers
    Clarity
    Easy to
    Change
    Guidelines
    Move Logic
    to Models
    Easy to
    Find

    View Slide

  15. • 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.
    15
    Scenario

    View Slide

  16. 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

    View Slide

  17. Rails Concerns
    17
    Big Model
    class macros (has_many, validates, etc.)
    instance methods
    class methods

    View Slide

  18. 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

    View Slide

  19. 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
    19

    View Slide

  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."
    20

    View Slide

  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
    21

    View Slide

  22. Objectives
    Patterns &
    Techniques
    22
    DRY
    Methods
    < 5 Lines
    Classes
    < 100 lines
    One Instance
    Variable in View
    Easy to
    Test
    Concerns
    Draper
    Decorators
    Validation
    Classes
    Presenters
    Split-up
    Controllers
    Clarity
    Easy to
    Change
    Guidelines
    Move Logic
    to Models
    Easy to
    Find

    View Slide

  23. 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.
    23

    View Slide

  24. Draper Decorators
    24
    Mode and
    Model-
    Concerns
    Presentation
    Code (views,
    helpers)

    View Slide

  25. Draper Decorators
    25
    Mode and
    Model-
    Concerns
    Presentation
    Code (views,
    helpers)
    Draper
    Decorators

    View Slide

  26. Draper Decorators: What?
    • Popular gem that facilitates model decorators

    • Very simple, easy to use
    26

    View Slide

  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
    27

    View Slide

  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
    28

    View Slide

  29. Draper Decorators: Alternatives
    • View Helpers

    • PORO, getting a handle to the controller or view
    29

    View Slide

  30. Example
    Several views have code that format the micropost.created_at:

    !
    Posted <%= time_ago_in_words(micropost.created_at) %> ago.
    30

    View Slide

  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.
    31

    View Slide

  32. Objectives
    Patterns &
    Techniques
    32
    DRY
    Methods
    < 5 Lines
    Classes
    < 100 lines
    One Instance
    Variable in View
    Easy to
    Test
    Concerns
    Draper
    Decorators
    Validation
    Classes
    Presenters
    Split-up
    Controllers
    Clarity
    Easy to
    Change
    Guidelines
    Move Logic
    to Models
    Easy to
    Find

    View Slide

  33. 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.
    33

    View Slide

  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
    34

    View Slide

  35. 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

    View Slide

  36. 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

    View Slide

  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.
    37

    View Slide

  38. Objectives
    Patterns &
    Techniques
    38
    DRY
    Methods
    < 5 Lines
    Classes
    < 100 lines
    One Instance
    Variable in View
    Easy to
    Test
    Concerns
    Draper
    Decorators
    Validation
    Classes
    Presenters
    Split-up
    Controllers
    Clarity
    Easy to
    Change
    Guidelines
    Move Logic
    to Models
    Easy to
    Find

    View Slide

  39. 39
    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

    View Slide

  40. –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?

    View Slide

  41. 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

    View Slide

  42. 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

    View Slide

  43. 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

    View Slide

  44. 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

    View Slide

  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”
    45

    View Slide

  46. Plain Rails
    46
    Big Micropost
    Create Action
    on Controller
    Micropost Model
    User Model
    Small Micropost
    Create Action on
    Controller
    before
    after

    View Slide

  47. 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”)
    47

    View Slide

  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
    48

    View Slide

  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
    49

    View Slide

  50. Objectives
    Patterns &
    Techniques
    50
    DRY
    Methods
    < 5 Lines
    Classes
    < 100 lines
    One Instance
    Variable in View
    Easy to
    Test
    Concerns
    Draper
    Decorators
    Validation
    Classes
    Presenters
    Split-up
    Controllers
    Clarity
    Easy to
    Change
    Guidelines
    Move Logic
    to Models
    Easy to
    Find

    View Slide

  51. 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

    View Slide

  52. 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

    View Slide

  53. Thanks!
    • More details at my blog:

    http://www.railsonmaui.com

    • Feel free to contact me
    regarding your projects

    [email protected]

    • http://airpair.me/railsonmaui
    53

    View Slide