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
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 )
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
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
global Which we promptly remedied with some monkey patching Ruby is awesome at this Rigid design that also limits functionality, what about next month?
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
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
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
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
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'
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!"
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
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
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
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.
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
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
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
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
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?
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
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.
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 ...
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
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
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
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
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
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
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
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