Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Sustainable Productivity: Rails vs OOP

Sustainable Productivity: Rails vs OOP

There is an ongoing debate in the Rails community as to whether properly applying certain well-known OO principles and techniques can improve the maintainability of our Rails applications as they grow.

However, often these practices differ from the traditional _Rails way_ which, after all, claims to be “optimised for programmer happiness and sustainable productivity”.

In this talk, we'll explore the different views of the community on this subject keeping the drama and the rhetoric out of the debate. We'll discuss about SOLID principles, cohesion, ceremony vs essence, data vs behaviour, indirection, testability, the framework as a detail and other related topics.

Hopefully, this talk will help you make better decisions when choosing solutions to improve the maintainability of your Rails codebase.

8a8d65bb7cec6df5ee7383532145b400?s=128

Luismi Cavallé

March 22, 2013
Tweet

Transcript

  1. 4VTUBJOBCMF 1SPEVDUJWJUZ 3BJMTWT001

  2. "UUIF CFHJOOJOH

  3. None
  4. &3# 5FTU6OJU .Z42-

  5. BLB 5IF (PMEFO 1BUI 5IF 3BJMT 8BZ

  6. ,*44

  7. %3:

  8. 4LJOOZ$POUSPMMFST 'BU.PEFMT http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model

  9. http://flic.kr/p/88SDCu

  10. http://flic.kr/p/88JHir

  11. http://flic.kr/p/88MWyf

  12. 4J[F $PNQMFYJUZ

  13. )BNM 34QFD $VDVNCFS 1PTUHSF42- 1SFTFOUFST 4FSWJDFT $PODFSOT 3PMFT %$* .PDLT

    )FYBHPOBM %%% &3# 5FTU6OJU .Z42- $P⒎F4DSJQU
  14. 4IPVMEXFEFWJBUF GSPNUIF(PMEFO 1BUIUPEP001 SJHIU

  15. 8IBUBSFUIF CFOFpUTPGVTJOH UIBUQBUUFSOPSUIBU UFDIOJRVF

  16. 8IBUBSFUIF QJUGBMMT

  17. /P TJOHMF BOTXFS

  18. $PIFTJPO$PVQMJOH 40-*%QSJODJQMFT %BUBWT3FTQPOTJCJMJUZ $FSFNPOZWT&TTFODF 5IFDPTUPGJOEJSFDUJPO 3BJMTWT001

  19. $PIFTJPO $PVQMJOH

  20. $MBTTJD 4PGUXBSF $PODFQUT

  21. #JH"DUJWF3FDPSE NPEFMTIBWFMPX DPIFTJPO

  22. !!class!Article!<!ActiveRecord::Base !!!!attr_accessor!:moderation_option ! !!!!attr_accessible!:text,!:reply_to_i ! !!!!belongs_to!:topic !!!!belongs_to!:user,!!!!!:class_name! !!!!belongs_to!:reply_to,!:class_name!

  23. !!class!Article!<!ActiveRecord::Base !!!!attr_accessor!:moderation_option ! !!!!attr_accessible!:text,!:reply_to_id,!:tag_names ! !!!!belongs_to!:topic !!!!belongs_to!:user,!!!!!:class_name!=>!Forem.user_class.to_s !!!!belongs_to!:reply_to,!:class_name!=>!"Post" ! !!!!has_many!:replies,!:class_name!!=>!"Post",

    !!!!!!!!!!!!!!!!!!!!!!!:foreign_key!=>!"reply_to_id", !!!!!!!!!!!!!!!!!!!!!!!:dependent!!!=>!:nullify !!!! !!!!has_many!:taggings !!!!has_many!:tags,!:through!=>!:taggings ! !!!!validates!:text,!:presence!=>!true ! !!!!delegate!:forum,!:to!=>!:topic ! !!!!after_create!:set_topic_last_post_at !!!!after_create!:skip_pending_review !!!!after_save!:email_topic_subscribers ! !!!!class!<<!self !!!!!!def!by_created_at !!!!!!!!order!:created_at !!!!!!end ! !!!!!!def!pending_review !!!!!!!!where!:state!=>!'pending_review' !!!!!!end ! !!!!!!def!spam !!!!!!!!where!:state!=>!'spam' !!!!!!end !!!!!! !!!!!!def!tagged_with(tag) !!!!!!!!joins(:taggings!=>!:tags).where(:tags!=>!{!:name!=>!tag!}) !!!!!!end ! !!!!!!def!visible !!!!!!!!joins(:topic).where(:forem_topics!=>!{!:hidden!=>!false!}) !!!!!!end !!!!end !!!! !!!!def!tag_names=(tag_list) !!!!!!assign_tag_list!tag_list !!!!end ! !!!!private ! !!!!def!subscribe_replier !!!!!!if!topic!&&!user !!!!!!!!topic.subscribe_user(user.id) !!!!!!end !!!!end ! !!!!def!set_topic_last_post_at !!!!!!topic.update_attribute(:last_post_at,!created_at) !!!!end ! !!!!def!blacklist_user !!!!!!user.update_attribute(:forem_state,!"spam")!if!user !!!!end !!!! !!!!def!assign_tag_list(tag_list) !!!!!!tag_names!=!tag_list.gsub(/\s+/,!"").split(",") !!!!!!existing!=!self.tags.map!{|t|!t.name!} !!!!!!(existing!@!tag_names).each!do!|name| !!!!!!!!self.tags.delete!Tag.find_by_name(name) !!!!!!end !!!!!!tag_names.each!do!|name| !!!!!!!!self.tags!<<!Tag.find_or_create_by_name(name) !!!!!!end! !!!!end !!end
  24. !!!belongs_to!:user,!!!!!:class_name!=>!Fore !!!belongs_to!:reply_to,!:class_name!=>!"Pos !!!has_many!:replies,!:class_name!!=>!"Post" !!!!!!!!!!!!!!!!!!!!!!:foreign_key!=>!"reply !!!!!!!!!!!!!!!!!!!!!!:dependent!!!=>!:nulli !!! !!!has_many!:taggings !!!has_many!:tags,!:through!=>!:taggings !!!validates!:text,!:presence!=>!true !!!delegate!:forum,!:to!=>!:topic

    !!!after_create!:set_topic_last_post_at !!!after_create!:skip_pending_review
  25. !!!!end !!!!def!pending_review !!!!!!where!:state!=>!'pending_review' !!!!end !!!!def!spam !!!!!!where!:state!=>!'spam' !!!!end !!!! !!!!def!tagged_with(tag) !!!!!!joins(:taggings!=>!:tags).where(:tags!=>!{!:name!=>!tag!})

    !!!!end !!!!def!visible !!!!!!joins(:topic).where(:forem_topics!=>!{!:hidden!=>!false!}) !!!!end !!end !! !!def!tag_names=(tag_list) !!!!assign_tag_list!tag_list !!end !!private
  26. !!!!!end !!!end !!! !!!def!tag_names=(tag_list) !!!!!assign_tag_list!tag_list !!!end !!!private !!!def!subscribe_replier !!!!!if!topic!&&!user

  27. ! !!!!def!blacklist_user !!!!!!user.update_attribute(:forem_state,!"spam")!if!user !!!!end !!!! !!!!def!assign_tag_list(tag_list) !!!!!!tag_names!=!tag_list.gsub(/\s+/,!"").split(",") !!!!!!existing!=!self.tags.map!{|t|!t.name!} !!!!!!(existing!@!tag_names).each!do!|name| !!!!!!!!self.tags.delete!Tag.find_by_name(name)

    !!!!!!end !!!!!!tag_names.each!do!|name| !!!!!!!!self.tags!<<!Tag.find_or_create_by_name(name) !!!!!!end! !!!!end !!end
  28. !!class!Article!<!ActiveRecord::Base !!!!attr_accessor!:moderation_option ! !!!!attr_accessible!:text,!:reply_to_id,!:tag_names ! !!!!belongs_to!:topic !!!!belongs_to!:user,!!!!!:class_name!=>!Forem.user_class.to_s !!!!belongs_to!:reply_to,!:class_name!=>!"Post" ! !!!!has_many!:replies,!:class_name!!=>!"Post",

    !!!!!!!!!!!!!!!!!!!!!!!:foreign_key!=>!"reply_to_id", !!!!!!!!!!!!!!!!!!!!!!!:dependent!!!=>!:nullify !!!! !!!!has_many!:taggings !!!!has_many!:tags,!:through!=>!:taggings ! !!!!validates!:text,!:presence!=>!true ! !!!!delegate!:forum,!:to!=>!:topic ! !!!!after_create!:set_topic_last_post_at !!!!after_create!:skip_pending_review !!!!after_save!:email_topic_subscribers ! !!!!class!<<!self !!!!!!def!by_created_at !!!!!!!!order!:created_at !!!!!!end ! !!!!!!def!pending_review !!!!!!!!where!:state!=>!'pending_review' !!!!!!end ! !!!!!!def!spam !!!!!!!!where!:state!=>!'spam' !!!!!!end !!!!!! !!!!!!def!tagged_with(tag) !!!!!!!!joins(:taggings!=>!:tags).where(:tags!=>!{!:name!=>!tag!}) !!!!!!end ! !!!!!!def!visible !!!!!!!!joins(:topic).where(:forem_topics!=>!{!:hidden!=>!false!}) !!!!!!end !!!!end !!!! !!!!def!tag_names=(tag_list) !!!!!!assign_tag_list!tag_list !!!!end ! !!!!private ! !!!!def!subscribe_replier !!!!!!if!topic!&&!user !!!!!!!!topic.subscribe_user(user.id) !!!!!!end !!!!end ! !!!!def!set_topic_last_post_at !!!!!!topic.update_attribute(:last_post_at,!created_at) !!!!end ! !!!!def!blacklist_user !!!!!!user.update_attribute(:forem_state,!"spam")!if!user !!!!end !!!! !!!!def!assign_tag_list(tag_list) !!!!!!tag_names!=!tag_list.gsub(/\s+/,!"").split(",") !!!!!!existing!=!self.tags.map!{|t|!t.name!} !!!!!!(existing!@!tag_names).each!do!|name| !!!!!!!!self.tags.delete!Tag.find_by_name(name) !!!!!!end !!!!!!tag_names.each!do!|name| !!!!!!!!self.tags!<<!Tag.find_or_create_by_name(name) !!!!!!end! !!!!end !!end
  29. r.BSUJO'PXMFS l1VUUPHFUIFS XIBUDIBOHFT UPHFUIFSz

  30. class!Article!<!ActiveRecord::Base !!include!Taggable

  31. module!Taggable !!extend!ActiveSupport::Concern !!included!do !!!!has_many!:taggings !!!!has_many!:tags,!through:!:taggings !!!!scope!:tagged_with,!1>(tag_name)!do !!!!!!joins(:tags).where(tags:!{!name:!tag_name!})! !!!!end !!end !!def!tag_list

    !!!!tags.pluck(:name).join(',!') !!end !!def!tag_list=(tag_list) !!!!assign_tag_list!tag_list !!end !!private !!def!assign_tag_list(tag_list) !!!!tag_names!=!tag_list.split(',').map(&:strip).uniq !!!!self.tags!=!tag_names.map!do!|tag_name|! !!!!!!Tag.where(name:!tag_name).first_or_initialize !!!!end !!end end
  32. $PVQMJOH

  33. $PIFTJPO 1VUUPHFUIFSXIBU DIBOHFTUPHFUIFS

  34. $PVQMJOH ,FFQTFQBSBUFE XIBUEPFTO`UDIBOHF UPHFUIFS

  35. http://www.flickr.com/photos/martintaylor/2301776382/

  36. ActiveRecord::Base

  37. %PNBJOMPHJDBOE 1FSTJTUFODFBSF UJHIUMZDPVQMFE

  38. 40-*% 1SJODJQMFT

  39. 4JOHMF3FTQPOTJCJMJUZ1SJODJQMF 0QFO$MPTFE1SJODJQMF -JTLPW4VCTUJUVUJPO1SJODJQMF *OUFSGBDF4FHSFHBUJPO1SJODJQMF %FQFOEFODZ*OWFSTJPO1SJODJQMF http://butunclebob.com/ArticleS.UncleBob.PrinciplesOfOod

  40. None
  41. 4JOHMF 3FTQPOTJCJMJUZ 1SJODJQMF

  42. 8IBUJTB SFTQPOTJCJMJUZ

  43. r6ODMF#PC l"SFBTPOGPS DIBOHFz

  44. 109 ________________________ 8 ________________________ SRP: The Single Responsibility Principle None

    but Buddha himself must take the responsibility of giving out occult secrets... — E. Cobham Brewer 1810–1897. Dictionary of Phrase and Fable. 1898. This principle was described in the work of Tom DeMarco1 and Meilir Page-Jones2. They called it cohesion. They defined cohesion as the functional relatedness of the elements of a 1. [DeMarco79], p310 2. [PageJones88], Chapter 6, p82. https://docs.google.com/file/d/0ByOwmqah_nuGNHEtcU5OekdDMkk/edit
  45. None
  46. 149 ________________________ 9 ________________________ SRP: The Single Responsibility Principle None

    but Buddha himself must take the responsibility of giving out occult secrets... — E. Cobham Brewer 1810–1897. Dictionary of Phrase and Fable. 1898. This principle was described in the work of Tom DeMarco1 and Meilir Page-Jones2. They called it cohesion. As we’ll see in Chapter 21, we have a more specific definition of cohe- sion at the package level. However, at the class level the definition is similar. SRP: The Single Responsibility Principle THERE SHOULD NEVER BE MORE THAN ONE REASON FOR A CLASS TO CHANGE. Consider the bowling game from Chapter 6. For most of its development the Game class was handling two separate responsibilities. It was keeping track of the current frame, and it was calculating the score. In the end, RCM and RSK separated these two responsibilities into two classes. The Game kept the responsibility to keep track of frames, and the Scorer got the responsibility to calculate the score. (see page 85.) 1. [DeMarco79], p310 2. [PageJones88], Chapter 6, p82. http://www.objectmentor.com/resources/articles/srp.pdf
  47. tomed to thinking of responsibility in groups. For exampl in

    Listing 9-1. Most of us will agree that this interface look functions it declares are certainly functions belonging to a However, there are two responsibilities being shown connection management. The second is data communicatio tions manage the connection of the modem, while the send cate data. Listing 9-1 Modem.java -- SRP Violation interface Modem { public void dial(String pno); public void hangup(); public void send(char c); public char recv(); }
  48. However, there are two responsibilities being sho onnection management. The

    second is data communic ons manage the connection of the modem, while the s te data. Should these two responsibilities be separated? A wo sets of functions have almost nothing in common. nt reasons. Moreover, they will be called from compl ons that use them. Those different parts will change fo }
  49. ng shown here. The first responsibility is munication. The dial

    and hangup func- e the send and recv functions communi- ated? Almost certainly they should. The mmon. They’ll certainly change for differ- completely different parts of the applica- ange for different reasons as well.
  50. However, there are two responsibilities being show connection management. The

    second is data communica tions manage the connection of the modem, while the se cate data. Should these two responsibilities be separated? Tha tion is changing. If the application changes in ways that a tion functions, then the design will smell of Rigidity b public void Dial(string pno); public void Hangup(); public void Send(char c); public char Recv(); } Listing 8-1 (Continued) Modem.cs -- SRP Violation
  51. However, there are two responsibilities being sho onnection management. The

    second is data communi ons manage the connection of the modem, while the s ate data. Should these two responsibilities be separated? T on is changing. If the application changes in ways tha on functions, then the design will smell of Rigidity nd read will have to be recompiled and redeployed m e two responsibilities should be separated as shown pplications from coupling the two responsibilities.
  52. However, there are two responsibilities being shown here. The first

    responsibility is connection management. The second is data communication. The dial and hangup func- tions manage the connection of the modem, while the send and recv functions communi- cate data. Should these two responsibilities be separated? That depends upon how the applica- tion is changing. If the application changes in ways that affect the signature of the connec- tion functions, then the design will smell of Rigidity because the classes that call send and read will have to be recompiled and redeployed more often than we like. In that case the two responsibilities should be separated as shown in Figure 8-3. This keeps the client applications from coupling the two responsibilities. public void Dial(string pno); public void Hangup(); public void Send(char c); public char Recv(); } Modem.cs -- SRP Violation + send(:char) + recv() : char Data Channel + dial(pno : String) + hangup() Connection «interface» «interface» WAT!
  53. l"MNPTUDFSUBJOMZz

  54. l*UEFQFOETz

  55. %PHNB1SBHNB

  56. If, on the other hand, the application is not changing

    in ways that cause the the two responsibilities to change at differen times, then there is no need to separate them. Indeed, separating them would smell of Needless Complexity. There is a corrolary here. An axis of change is only an axis of change if the changes actually occurr. It is not wise to apply the SRP, or any other principle for that matter, if there is no symptom. Figure 8-3 Separated Modem Interface + recv() : char + hangup() Modem Implementation
  57. If, on the other hand, the application is not changing

    in ways that cause the the two responsibilities to change at differen times, then there is no need to separate them. Indeed, separating them would smell of Needless Complexity. There is a corrolary here. An axis of change is only an axis of change if the changes actually occurr. It is not wise to apply the SRP, or any other principle for that matter, if there is no symptom. Figure 8-3 Separated Modem Interface + recv() : char + hangup() Modem Implementation
  58. If, on the other hand, the application is not changing

    in ways that cause the the two responsibilities to change at differen times, then there is no need to separate them. Indeed, separating them would smell of Needless Complexity. There is a corrolary here. An axis of change is only an axis of change if the changes actually occurr. It is not wise to apply the SRP, or any other principle for that matter, if there is no symptom. Figure 8-3 Separated Modem Interface Modem Implementation
  59. ActiveRecord::Base

  60. )PXPGUFOUIF QFSTJTUFODFTUSBUFHZ DIBOHFT

  61. hand, the application is not changing i hange at differen

    times, then there is no uld smell of Needless Complexity. olary here. An axis of change is only an s not wise to apply the SRP, or any oth . Separated Modem Interface
  62. %BUBWT 3FTQPOTJCJMJUZ

  63. None
  64. r(SFHPSZ.PFDL *TTVF 3FTQPOTJCJMJUZDFOUSJD WTEBUBDFOUSJD EFTJHO https://practicingruby.com/articles/shared/bxstlrbnrjzt

  65. %BUB DFOUSJD

  66. .PEFMMJOH BSPVOE EBUB

  67. 3FBMXPSME FOUJUJFT

  68. 1FSTPO 0SEFS #MPH $PNQBOZ $PNNFOU 5BTL 1SPKFDU 1PTU

  69. #FIBWJPVS BUUBDIFEUP &OUJUJFT

  70. -PXDPIFTJPO

  71. None
  72. None
  73. 3FTQPOTJCJMJUZ DFOUSJD

  74. .PEFMMJOH BSPVOE CFIBWJPVS

  75. 4OJQFS4UBUF%JTQMBZFS 0SEFS1SPDFTTPS 1PTU*OEFYFS 1SPKFDU%BUBCBTF.BQQFS "VDUJPO4OJQFS %PDVNFOU'JOEFS

  76. %BUBJT KVTUEBUB

  77. 431JTFBTZ

  78. "OBFNJD.PEFM

  79. None
  80. /PU3BJMT TUZMF

  81. %BUB 3FTQPOTJCJMJUZ 3FBMXPSMEFOUJUJFT 4PMVUJPOSFBMNBSUJGBDUT 3JDI.PEFM "OBFNJD.PEFM -PXDPIFTJPO 431 4MPXUFTUT 2VJDLUFTUT

    3BJMT +&&
  82. $FSFNPOZ WT&TTFODF

  83. public!class!HelloWorld!{ !!!!public!static!void!main(String[]!args)!{ !!!!!!!!System.out.println("Hello,!World"); !!!!} } puts!'Hello,!World' WT

  84. SECS_IN_ONE_DAY!=!24!*!60!*!60 Time.now!1!7!*!SECS_IN_ONE_DAY 7.days.ago WT

  85. class!Catalog !!attr_accessor!:films,!:series !! !!def!initialize(films,!series) !!!!@films!!=!films !!!!@series!=!series !!end class!Catalog!<!Struct.new(:films,!:series) WT

  86. Publisher.new(post).publish post.publish WT

  87. 4UVBSU)BMMPXBZ &OEJOH-FHBDZ $PEF*O0VS -JGFUJNF http://thinkrelevance.com/blog/2008/04/01/ending-legacy-code-in-our-lifetime

  88. 'BJMTUPDBQUVSFJOUFOU 'BJMTUPDPNNVOJDBUFJOUFOU $BQUVSFTJSSFMFWBOUEFUBJM

  89. &TTFODF*OUFOU

  90. $FSFNPOZ*SSFMFWBOU%FUBJM

  91. &⒎FDUJWFMZ DPNNVOJDBUJOH JOUFOU

  92. 4/3

  93. 'MFYJCJMJUZIBTB DPTUJOUFSNTPG $FSFNPOZ

  94. 5IFDPTUPG JOEJSFDUJPO

  95. None
  96. s a d g h D c w f t

    i n a c t £È £x £{ £Î £Ó ££ £ä ™ n Ç È x { Î Ó £ œ}ÓʜvÊ̅iÊ Õ“LiÀʜvÊ œ“«œ˜i˜ÌÃÊ *iÀʘÌi}À>Ìi`Ê՘V̈œ˜ 9i>À £™x™ £™Èä £™È£ £™ÈÓ £™ÈÎ £™È{ £™Èx £™ÈÈ £™ÈÇ £™Èn £™È™ £™Çä £™Ç£ £™ÇÓ £™ÇÎ £™Ç{ £™Çx
  97. None
  98. Psychological Review © by the American Psychological Association Vol. 101,

    No. 2, 343-352 For personal use only--not for distribution. The Magical Number Seven, Plus or Minus Two Some Limits on Our Capacity for Processing Information George A. Miller Harvard University This paper was first read as an Invited Address before the Eastern Psychological Association in Philadelphia on April 15, 1955. Preparation of the paper was supported by the Harvard Psycho-Acoustic Laboratory under Contract N5ori-76 between Harvard University and the Office of Naval Research, U.S. Navy (Project NR 142-201, Report PNR-174). Reproduction for any purpose of the U.S. Government is permitted. Received: May 4, 1955 My problem is that I have been persecuted by an integer. For seven years this number has followed me around, has intruded in my most private data, and has assaulted me from the pages of our most public journals. This number assumes a variety of disguises, being sometimes a little larger and sometimes a little smaller than usual, but never changing so much as to be unrecognizable. The persistence with which this number plagues me is far more than a random accident. There is, to quote a famous senator, a design behind it, some pattern governing its appearances. Either there really is something unusual about the number or else I am suffering from delusions of persecution.
  99.       

  100. your_brain.stack.length!>!7 =>!false

  101. ./app/models/concerns/availability.rb:89:in!`reference_program' ./app/models/concerns/availability.rb:87:in!`reference_program' ./app/models/scheduling.rb:220:in!`reference_program' ./app/models/rule.rb:171:in!`process_rule_schedulings!' ./app/models/rule.rb:170:in!`process_rule_schedulings!' ./app/models/rule.rb:60:in!`apply' ./app/controllers/rules_controller.rb:29:in!`update' ./app/models/user.rb:38:in!`with_current' ./app/controllers/application_controller.rb:70:in!`with_current_user' ./spec/acceptance/support/auth.rb:24:in!`call'

    ./lib/params_api_parser.rb:14:in!`call' ./spec/acceptance/support/webrat_compatibility.rb:164:in!`post' (eval):2:in!`send' (eval):2:in!`click_button' ./spec/acceptance/edit_vod_scheduling_rules_spec.rb:151 ./spec/acceptance/support/webrat_compatibility.rb:38:in!`call' ./spec/acceptance/support/webrat_compatibility.rb:38:in!`within' ./spec/acceptance/support/webrat_compatibility.rb:37:in!`within' ./spec/acceptance/edit_vod_scheduling_rules_spec.rb:148
  102. FATAL: stack!level!too!deep

  103. "CTUSBDUJPOIBTB DPTUJOUFSNTPG *OEJSFDUJPO

  104. http://zedshaw.com/essays/indirection_is_not_abstraction.html

  105. r;FE4IBX l"OVOCFMJFWBCMFMFWFMTPG JOEJSFDUJPOKVTUUPBEEB GVDLJOHPCKFDUUPBGVDLJOH DPMMFDUJPO5IJTJTUIFLJOEPG CVMMTIJUUIBUDIBQTNZBTT QVSQMFBOENBLFTNFXBOUUP FBUCBCJFTz http://zedshaw.com/essays/indirection_is_not_abstraction.html

  106. 3BJMTWT001

  107. $POqJDUJOH USBEFP⒎T

  108. 3PPNGPS EFCBUF

  109. 3BJMT EFWJBUFT GSPNUIF00 EPHNB

  110. *TUIBUPL PSOPU

  111. RAILS AS SHE IS SPOKE (BY GILES BOWKETT) HOW RAILS

    BREAKS OBJECT-ORIENTED THEORY, AND WHY IT WORKS ANYWAY COPYRIGHT 2012 GILES BOWKETT http://railsoopbook.com
  112. l3BJMTNBLFTHPPE VTFSFYQFSJFODFJUT QSJNBSZHPBM XIFSF bVTFS`NFBOTUIFBQQ EFWFMPQFSz

  113. 69PWFS 001EPHNB

  114. "UUIF CFHJOOJOH

  115. BLB 5IF (PMEFO 1BUI 5IF 3BJMT 8BZ

  116. )BNM 34QFD $VDVNCFS 1PTUHSF42- 1SFTFOUFST 4FSWJDFT $PODFSOT 3PMFT %$* .PDLT

    )FYBHPOBM %%% &3# 5FTU6OJU .Z42- $P⒎F4DSJQU
  117. e(SBDJBT -VJTNJ$BWBMMÉ UXJUUFSDPNDBWBMMF HJUIVCDPNDBWBMMF QJOCPBSEJOVDBWBMMFUCJHNPEFMT