Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

3 Controller Model &

Slide 4

Slide 4 text

Avoid the Ball of Mud May seem fun… ! Guaranteed: the perpetrator is not doing the cleanup! 4

Slide 5

Slide 5 text

5 Sandi Rules method > 5 lines class > 100 lines

Slide 6

Slide 6 text

6 How do we organize the mess?

Slide 7

Slide 7 text

Organizational Conventions Matter 7 Department Store

Slide 8

Slide 8 text

Organizational Conventions Matter 8 Thrift Store

Slide 9

Slide 9 text

Like Fashion… Coding Style ➜ Personal Preference 9

Slide 10

Slide 10 text

And Our Style is… 10

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

• 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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Draper Decorators: What? • Popular gem that facilitates model decorators • Very simple, easy to use 26

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

Draper Decorators: Alternatives • View Helpers • PORO, getting a handle to the controller or view 29

Slide 30

Slide 30 text

Example Several views have code that format the micropost.created_at: ! Posted <%= time_ago_in_words(micropost.created_at) %> ago. 30

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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