Slide 1

Slide 1 text

IMPROVE YOUR CODE WITH DEPENDENCY INJECTION EXAMPLES IN RUBY :) Hand crafted by: Stephen Best / / github.com/bestie @thebestie

Slide 2

Slide 2 text

WHAT IS DEPENDENCY INJECTION? (QUICKLY BECAUSE WE'RE BORED ALREADY!) "DI is the practice of replacing hardcoded classes or types with dynamically configurable objects."

Slide 3

Slide 3 text

WHY IS IT USEFUL? Enables true isloated unit testing Increases re-usability of individual objects Loosens coupling system wide, increases flexibility

Slide 4

Slide 4 text

SO MY CODE IS STICKY AND RIGID. WHAT ELSE?

Slide 5

Slide 5 text

IT'S FULL OF GLOBAL VARIBLES In Ruby constants are global And can be overwritten Generally a Ruby app will have class objects stored in constants Classes == Global variables c l a s s F o o # . . . e n d F o o = C l a s s . n e w d o # . . . e n d $ f o o _ c l a s s = C l a s s . n e w d o # . . . e n d

Slide 6

Slide 6 text

AND YOU'VE GOT MONKEY PATCHING EVERYWHERE TOO We frequently write code like this # T h i s i s y o u r t e s t i n g f r a m e w o r k i s m o n k e y p a t c h i n g f o r y o u . U s e r . s t u b ( : n e w ) . a n d _ r e t u r n ( d o u b l e )

Slide 7

Slide 7 text

FRUIT OF THE MONTH CLUB Subscribers receive a box of fruit Every month The fruit changes It's going to be HUGE

Slide 8

Slide 8 text

WHICH FRUITS ARE IN SEASON NOW? c l a s s F r u i t < A c t i v e R e c o r d : : B a s e d e f s e l f . i n _ s e a s o n d a t e = D a t e . t o d a y a l l . w h e r e ( [ " s e a s o n _ s t a r t < = ? " , d a t e ] ) . w h e r e ( [ " s e a s o n _ e n d > = ? " , d a t e ] ) e n d e n d

Slide 9

Slide 9 text

WHICH FRUITS ARE IN SEASON NOW? d e s c r i b e F r u i t d o d e s c r i b e " . i n _ s e a s o n " d o b e f o r e { s e e d _ f r u i t s } c o n t e x t " i n s u m m e r t i m e " d o b e f o r e { D a t e . s t u b ( : t o d a y ) . a n d _ r e t u r n ( " 2 0 1 3 - 0 7 - 0 7 " ) } i t " i n c l u d e s s u m m e r f r u i t s " d o e x p e c t ( F r u i t s . i n _ s e a s o n ) . t o i n c l u d e ( s u m m e r _ f r u i t s ) e n d i t " e x c l u d e s w i n t e r f r u i t s " d o e x p e c t ( F r u i t s . i n _ s e a s o n ) . n o t _ t o i n c l u d e ( w i n t e r _ f r u i t s ) e n d e n d e n d e n d

Slide 10

Slide 10 text

SO WHAT WAS WRONG? Classic example of an unnecessarily hardcoded global Which we promptly remedied with some monkey patching Ruby is awesome at this Rigid design that also limits functionality, what about next month?

Slide 11

Slide 11 text

A SMALL BUT CRUCIAL CHANGE c l a s s F r u i t s d e f s e l f . i n _ s e a s o n ( d a t e = D a t e . t o d a y ) a l l . w h e r e ( [ " s e a s o n _ s t a r t < = ? " , d a t e ] ) . w h e r e ( [ " s e a s o n _ e n d > = ? " , d a t e ] ) e n d e n d

Slide 12

Slide 12 text

TESTS BECOME LESS COMPLEX d e s c r i b e F r u i t d o d e s c r i b e " . i n _ s e a s o n " d o b e f o r e { s e e d _ f r u i t s _ i n _ d b } c o n t e x t " i n s u m m e r t i m e " d o l e t ( : d a t e ) { " 2 0 1 3 - 0 7 - 0 7 " } i t " i n c l u d e s s u m m e r f r u i t s " d o e x p e c t ( F r u i t s . i n _ s e a s o n ( d a t e ) ) . t o i n c l u d e ( s u m m e r _ f r u i t s ) e n d i t " e x c l u d e s w i n t e r f r u i t s " d o e x p e c t ( F r u i t s . i n _ s e a s o n ( d a t e ) ) . n o t _ t o i n c l u d e ( w i n t e r _ f r u i t s ) e n d e n d e n d e n d

Slide 13

Slide 13 text

FRUIT SEARCH FEATURE For the connoisseur Lots of search options, origin, colour, sweetness etc...

Slide 14

Slide 14 text

The crack team write something reasonable Starting with a service object c l a s s F r u i t s C o n t r o l l e r < A p p l i c a t i o n C o n t r o l l e r d e f s e a r c h @ f r u i t s = C o m p l e x F r u i t S e a r c h . n e w . r e s u l t s ( p a r a m s ) e n d e n d

Slide 15

Slide 15 text

The crack team write something reasonable c l a s s C o m p l e x F r u i t S e a r c h d e f r e s u l t s ( p a r a m s ) @ p a r a m s = p a r a m s F r u i t . w h e r e ( o r m _ f r i e n d l y _ p a r a m s ) e n d p r i v a t e d e f o r m _ f r i e n d l y _ p a r a m s # l o t s o f c o m p l e x l o g i c e n d e n d

Slide 16

Slide 16 text

WHAT COULD POSSIBLY BE WRONG NOW? Fruit happens to be an ActiveRecord This makes tests slow (loading Rails) Not open to re-use

Slide 17

Slide 17 text

THE ONE CONSTANT IS CHANGE The boss asks for scoped search pages Exotic fruits Seasonal fruits Seedless fruits How do we do this?

Slide 18

Slide 18 text

c l a s s C o m p l e x F r u i t S e a r c h d e f r e s u l t s ( p a r a m s , o p t i o n s = { } ) @ p a r a m s = p a r a m s s c o p e = o p t i o n s . f e t c h ( : s c o p e ) { F r u i t . a l l } s c o p e . w h e r e ( o r m _ f r i e n d l y _ p a r a m s ) e n d p r i v a t e d e f o r m _ f r i e n d l y _ p a r a m s # l o t s o f c o m p l e x l o g i c e n d e n d This refactoring gives us variable ORM scope and isolated / fast tests achieved with some 'fake DI'

Slide 19

Slide 19 text

ONE LAST THING. WHO ARE YOU? SOME KIND OF STEVE JOBS? Product manager: "Loved the work on Fruits, we're adding vegetables too" Developer: "OK cool well that should be easy since we've been leveraging DI in our app!" Product manager: "Whatever dork, just get on with it!"

Slide 20

Slide 20 text

SURE THERE'S DI, BUT IT COULD BE BETTER c l a s s V e g e t a b l e s S e a r c h C o n t r o l l e r < A p p l i c a t i o n C o n t r o l l e r d e f i n _ s e a s o n @ r e s u l t s = C o m p l e x P r o d u c e S e a r c h . n e w . r e s u l t s ( s c o p e : V e g e t a b l e . i n _ s e a s o n , p a r a m s : p a r a m s , ) e n d d e f p u l s e s @ r e s u l t s = C o m p l e x P r o d u c e S e a r c h . n e w . r e s u l t s ( s c o p e : V e g e t a b l e . p u l s e s , p a r a m s : p a r a m s , ) e n d e n d

Slide 21

Slide 21 text

DI + CONFIGURABLE OBJECTS = JOY c l a s s V e g e t a b l e s S e a r c h C o n t r o l l e r < A p p l i c a t i o n C o n t r o l l e r d e f i n _ s e a s o n @ r e s u l t s = v e g _ s e a r c h . r e s u l t s ( s c o p e : : i n _ s e a s o n , p a r a m s : p a r a m s , ) e n d d e f p u l s e s @ r e s u l t s = v e g _ s e a r c h . r e s u l t s ( s c o p e : : p u l s e s , p a r a m s : p a r a m s , ) e n d p r i v a t e d e f v e g _ s e a r c h C o m p l e x P r o d u c e S e a r c h . n e w ( t y p e : V e g e t a b l e ) e n d e n d

Slide 22

Slide 22 text

FRUIT + VEGETABLES = PRODUCE c l a s s C o m p l e x P r o d u c e S e a r c h d e f i n i t i a l i z e ( d e p e n d e n c i e s ) # N o w t h i s c a n s e a r c h e v e n v e g e t a b l e s ! @ t y p e = d e p e n d e n c i e s . f e t c h ( : t y p e ) e n d d e f r e s u l t s ( a r g s ) @ p a r a m s = a r g s . f e t c h ( : p a r a m s ) @ s c o p e = a r g s . f e t c h ( : s c o p e , : a l l ) t y p e . p u b l i c _ s e n d ( s c o p e ) . w h e r e ( o r m _ f r i e n d l y _ p a r a m s ) e n d p r i v a t e a t t r _ r e a d e r : t y p e , : p a r a m s , : s c o p e d e f o r m _ f r i e n d l y _ p a r a m s # l o t s o f c o m p l e x l o g i c e n d e n d

Slide 23

Slide 23 text

TWO IMPORTANT THINGS JUST HAPPENED A hardcoded dependency became a runtime configuration option Eliminated the default value and decoupled from the specific class name The Gang of Four would be proud, we just programmed to an interface not an implementation.

Slide 24

Slide 24 text

PROGRAMME TO A DUCK, NOT A MALLARD # C o m p l e x P r o d u c e S e a r c h i s a m a l l a r d @ r e s u l t s = C o m p l e x P r o d u c e S e a r c h . n e w ( t y p e : F r u i t ) . r e s u l t s ( s c o p e : : i n _ s e a s o n , p a r a m s : p a r a m s , ) # f r u i t _ s e a r c h i s a d u c k @ r e s u l t s = f r u i t _ s e a r c h . r e s u l t s ( s c o p e : : i n _ s e a s o n , p a r a m s : p a r a m s , ) Ducks and Mallards will preferably have different names Name classes according to their implementations Name collaborators according to role

Slide 25

Slide 25 text

THAT'S ALL VERY WELL BUT THE MALLARDS HAVE GOTTEN EVERYWHERE WHAT CAN WE DO? TODO: INSERT SOLUTION

Slide 26

Slide 26 text

OBJECT CONSTRUCTION Consider the difference between the following. # C l a s s m e t h o d C o m p l e x P r o d u c e S e a r c h . r e s u l t s ( F r u i t . i n _ s e a s o n , p a r a m s ) Not OO More like a namespaced procedure Implementation will be hard to refactor

Slide 27

Slide 27 text

OBJECT CONSTRUCTION # P a s s e v e r y t h i n g t o n e w C o m p l e x P r o d u c e S e a r c h . n e w ( F r u i t . i n _ s e a s o n , p a r a m s ) . r e s u l t s We're instantiating an object (great) But the object isn't re-usable The client or creator of this object needs to know all the dependencies at once

Slide 28

Slide 28 text

SEPARATE 'COMPILE TIME' AND 'RUNTIME' DEPENDENCIES OK so Ruby doesn't exactly compile BUT... # S e p a r a t e d e p e n d e n c i e s a n d i n p u t s C o m p l e x P r o d u c e S e a r c h . n e w ( F r u i t ) . r e s u l t s ( : i n _ s e a s o n , p a r a m s ) The Fruit type is known well in advance The params and the scope are user inputs Instantiation and invocation have now been separated

Slide 29

Slide 29 text

INSTANTIATION AS A SEPERATE CONCERN c l a s s V e g e t a b l e s S e a r c h C o n t r o l l e r < A p p l i c a t i o n C o n t r o l l e r d e f i n _ s e a s o n @ r e s u l t s = a p p . v e g _ s e a r c h . c a l l ( s c o p e : : i n _ s e a s o n , p a r a m s : p a r a m s , ) e n d # . . . e n d No instantiation What's app exactly?

Slide 30

Slide 30 text

YOUR APP COULD BE AN OBJECT TOO! c l a s s F r u i t O f T h e M o n t h A p p d e f v e g _ s e a r c h C o m p l e x P r o d u c e S e a r c h . n e w ( V e g e t a b l e ) e n d # . . . e n d This is the one place class names can be found Contains no logic beyond simple factory methods Is essentially a config file This pattern is called Service Locator Keeps all your mallards in one place

Slide 31

Slide 31 text

HOW DO I GET THIS INTO RAILS? # c o n f i g / i n i t i a l i z e r s / f r u i t _ o f _ t h e _ m o n t h _ a p p . r b A P P = F r u i t O f T h e M o n t h A p p . n e w # a p p / c o n t r o l l e r s / a p p l i c a t i o n _ c o n t r o l l e r . r b c l a s s A p p l i c a t i o n C o n t r o l l e r d e f a p p A P P e n d # . . . e n d Your app object need not be a singleton, use a constant and instantiate just one.

Slide 32

Slide 32 text

SOME FUNCTIONAL TRICKS c l a s s C r e a t e U s e r d e f c a l l ( p a r a m s ) u s e r = U s e r . n e w ( p a r a m s ) # o t h e r c o m p l e x l o g i c . . . e n d e n d Now with DI ...

Slide 33

Slide 33 text

c l a s s C r e a t e U s e r d e f i n i t i a l i z e ( o p t i o n s ) @ u s e r _ c l a s s = o p t i o n s . f e t c h ( : u s e r _ c l a s s ) e n d d e f c a l l ( p a r a m s ) u s e r = @ u s e r _ c l a s s . n e w ( p a r a m s ) # o t h e r c o m p l e x l o g i c . . . e n d e n d User constuction is now injected It's still coupled to the class level implementation and we hate those

Slide 34

Slide 34 text

DECOUPLE WITH THE #CALL PROTOCOL c l a s s C r e a t e U s e r d e f i n i t i a l i z e ( o p t i o n s ) @ u s e r _ b u i l d e r = o p t i o n s . f e t c h ( : u s e r _ b u i l d e r ) e n d d e f c a l l ( p a r a m s ) u s e r = @ u s e r _ b u i l d e r . c a l l ( p a r a m s ) # o t h e r c o m p l e x l o g i c . . . e n d e n d User builder can be anything that responds to call Procs, blocks and lambdas Your objects with a #call method Ruby method objects

Slide 35

Slide 35 text

DID YOU ACTUALLY SAY METHOD METHOD? c l a s s F r u i t O f T h e M o n t h A p p d e f c r e a t e _ u s e r C r e a t e U s e r . n e w ( u s e r _ b u i l d e r : U s e r . m e t h o d ( : n e w ) , ) e n d e n d #method plucks the method off the object Behaves like a lambda responds to #call Doesn't lose its binding like in Javascript Easily replaced with a more complex object later Great trick for interface segregation

Slide 36

Slide 36 text

CURRYING > f u n c = l a m b d a { | a , b , c | [ a , b , c ] . j o i n ( " a n d " ) } > f u n c . c u r r y . c a l l ( " A " ) . c a l l ( " B " ) . c a l l ( " C " ) = > " A a n d B a n d C " Another great tool for separating timings of arguments Only works on positional arguments :( Both your FP and OOP friends will think it's cool No one will even notice it's a factory factory

Slide 37

Slide 37 text

COMING IN RUBY 2.1 Required keyword arguments > d e f s a y _ h e l l o ( t o : , f r o m : ) > p u t s " # { t o } , # { f r o m } s a y s h i ! " > e n d = > : s a y _ h e l l o > s a y _ h e l l o ( f r o m : " B e s t i e " ) A r g u m e n t E r r o r : m i s s i n g k e y w o r d : t o > s a y _ h e l l o ( f r o m : " B e s t i e " , t o : " S c o t R U G " ) S c o t R U G B e s t i e s a y s h i ! = > n i l

Slide 38

Slide 38 text

CURRYING KEYWORD ARGUMENTS There's a gem for that! github.com/bestie/keyword_curry Works for required keywords only

Slide 39

Slide 39 text

KEYWORD CURRYING EXAMPLE > r e q u i r e " k e y w o r d _ c u r r y " = > t r u e > K e y w o r d C u r r y . m o n k e y _ p a t c h _ p r o c = > P r o c > d e f s a y _ h e l l o ( t o : , f r o m : ) > p u t s " # { t o } , # { f r o m } s a y s h i ! " > e n d = > : s a y _ h e l l o > m e t h o d ( : s a y _ h e l l o ) . t o _ p r o c . c u r r y . c a l l ( f r o m : " B e s t i e " ) . c a l l ( t o : " S c o t R U G " ) S c o t R U G B e s t i e s a y s h i ! = > n i l

Slide 40

Slide 40 text

IF LIKE ME YOU ALSO LOVE #FETCH There's a gem for that too. github.com/bestie/fetchable

Slide 41

Slide 41 text

SUMMARY DI gives us ... More focused AND faster testing More flexible code Code that can do different things when introduced to new collaborators Reduced need to change existing objects A clear path to acheiving the 'O', 'I' and 'D' of SOLID

Slide 42

Slide 42 text

START TODAY! With unevaluated default arguments c l a s s D I H a t e r d e f i n i t i a l i z e ( o u t l e t = T w i t t e r . n e w ) # . . . e n d e n d

Slide 43

Slide 43 text

START TODAY! Or with Hash#fetch c l a s s D I H a t e r d e f i n i t i a l i z e ( a r g s ) @ h a t e _ o u t l e t = a r g s . f e t c h ( : h a t e _ o u t l e t ) { T w i t t e r . n e w } e n d e n d

Slide 44

Slide 44 text

WHO WAS THAT GUY? STEPHEN BEST Contract Ruby / Javascript developer Cambridge Healthcare - howareyou.com @thebestie github.com/bestie