Rails Conf 2014 Concerns, Decorators, Presenters, Service-objects, Helpers, Help Me...

Post on 11-May-2015

4.801 views 2 download

description

Slides Ruby Conf 2014 on using simple techniques to create slimer, clearer models, controllers, and views in Ruby on Rails. blog: http://www.railsonmaui.com Code samples: https://github.com/justin808/fat-code-refactoring-techniques

Transcript of Rails Conf 2014 Concerns, Decorators, Presenters, Service-objects, Helpers, Help Me...

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

• justin@railsonmaui.com

• http://airpair.me/railsonmaui

53