Solid Designfor Rails applications           Matteo Vaccari    matteo.vaccari@xpeppers.com         www.xpeppers.com    Ita...
Revelations•   1977 - listato BASIC su rivista di elettronica•   1984 - prima login su Unix•   1994 - lezione di Dijkstra•...
There are a few things I look for that are good predictors of whether aproject is in good shape.Once and only once ...Lots...
Objects in a Rails project: •   Models (one per DB table) •   Helpers (one per controller) •   Controllers (~ one per DB t...
What’s the problem?•   Maintainability•   Long-term maintainability•   But, in general, maintainability
In a good project...•   The cost of delivering features decreases    over time
Idea #0: embrace REST
Verbs and nouns•   GET             mailto:vaccari@pobox.com•   POST     http://matteo.vaccari.name/blog•   PUT      http:/...
REST and CRUD•   GET      • index, show, new, edit   • SELECT•   POST     • create                   • INSERT•   PUT      ...
Embrace REST                          RPCcart: show, add_item, remove_item, add_coupon, removecoupon, increase_quantity, d...
Embrace RESTScott Raymond, Refactoring to REST, 2006/6/20Before refactoring, IconBuffet had 10 controllers and76 actions. ...
Connect the dots...•   REST thinking reduces complex domains to    CRUDs• Rails makes it easy to do CRUDs• Rails makes it ...
Idea #1: embrace OOP
Boolean configurations            bring IFsSTORES_CONFIGURATION[:foo] = {  :tracking_email_enabled           => true,  :sim...
OK. Nessuno te l’ha  detto finora ma...Aggiungere IF è il male.
Francesco CirilloCOMODO ≠ EFFICACE           http://www.antiifcampaign.com/
http://pierg.wordpress.com/2009/08/05/anti-if-campaign/
STORES_CONFIGURATION[:foo] = {  :tracking_email_enabled             => true,  :simple_agency_tracking_enabled     => true,...
Fat modelsclass Product < ActiveRecord::Base  # ... 363 lines ...end
class Product < ActiveRecord::Base # ... named_scope :full_text_search, lambda { |keywords|   if keywords.blank?     { :co...
end  }  private  def self.whitelist_characters_for_search(keywords)    # ...  end  def self.prepare_for_regexp_search(keyw...
Cure: use compositionclass Product < ActiveRecord::Base  # ...  named_scope :full_text_search, lambda { |keywords|    Full...
Eventually...  class Product < ActiveRecord::Base    extend ProductFinders    include ProductCategoryMethods    include Tr...
Tediumit "displays information for a given user" do  Factory.create(:user, :id => "1234", :first_name => "Arthur")  get "/...
it "displays information for a given user" do  Factory.create(:user, :id => "1234",    :first_name => "Arthur", :last_name...
it "displays information for a given user" do      Factory.create(:user, :id => "1234",        :first_name => "Arthur", :l...
Useless IDs!!!                                                   Duplication!!!<table>  <tr class="even">    <td><strong>F...
it "displays information for a given user" do                       Factory.create(:user, :id => "1234",                  ...
The original definition of TDD says:1.Quickly add a test.2.Run all tests and see the new one fail.3.Make a little change.4....
<table>  <tr class="even">    <td><strong>First Name</strong></td>    <td id="user_first_name"><%= @user.first_name %></td...
<%=   AdminTable.new(@user.attributes_for_administration).to_html%>it "produces an html table" do  model = [["Label 0", "v...
A nonobvious conclusionSolving a slightly more general problem than strictly  necessary is often easier, simpler and clean...
Use every weapon•   Use objects in place of IFs•   Split models, delegate to objects and modules•   Refactor views! Use he...
Want to know more?
Want to know more?   The Clean Code Talks - Dont Look For Things!            Miško Hevery       The Clean Code Talks #2
Want to know more?     Read chapter one!
Want to know more?  http://www.antiifcampaign.com/
Want to know more?   http://matteo.vaccari.name/blog/    matteo.vaccari@xpeppers.com         twitter: @xpmatteoThis presen...
Grazie dell’attenzione! Extreme Programming:development & mentoring
Upcoming SlideShare
Loading in …5
×

Solid design for Rails

3,836 views

Published on

My presentation at the Italian Ruby Day

Published in: Technology
1 Comment
7 Likes
Statistics
Notes
  • If you like this presentation, check out the Rails Antipatterns book
    http://my.safaribooksonline.com/book/web-development/ruby/9780321620293
    and also this valuable blog post:
    http://blog.steveklabnik.com/2011/09/06/the-secret-to-rails-oo-design.html
    -- Matteo
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total views
3,836
On SlideShare
0
From Embeds
0
Number of Embeds
1,398
Actions
Shares
0
Downloads
24
Comments
1
Likes
7
Embeds 0
No embeds

No notes for slide

Solid design for Rails

  1. 1. Solid Designfor Rails applications Matteo Vaccari matteo.vaccari@xpeppers.com www.xpeppers.com Italian Ruby Day, 2011/06/10 (cc) Some rights reserved
  2. 2. Revelations• 1977 - listato BASIC su rivista di elettronica• 1984 - prima login su Unix• 1994 - lezione di Dijkstra• 2004 - Extreme Programming!• 2005 - Ruby on Rails• 2009 - Object-Oriented Design
  3. 3. There are a few things I look for that are good predictors of whether aproject is in good shape.Once and only once ...Lots of little pieces - Good code invariably has small methods and smallobjects. Only by factoring the system into many small pieces of stateand function can you hope to satisfy the “once and only once” rule. ...Replacing objects - Good style leads to easily replaceable objects. In areally good system, every time the user says “I want to do thisradically different thing,” the developer says, “Oh, I’ll have to make anew kind of X and plug it in.” ...Kent Beck – Smalltalk Best Practice Patterns
  4. 4. Objects in a Rails project: • Models (one per DB table) • Helpers (one per controller) • Controllers (~ one per DB table) • Views (~ 7 * controller)The number of objects is somehow fixed Objects are rarely reusable
  5. 5. What’s the problem?• Maintainability• Long-term maintainability• But, in general, maintainability
  6. 6. In a good project...• The cost of delivering features decreases over time
  7. 7. Idea #0: embrace REST
  8. 8. Verbs and nouns• GET mailto:vaccari@pobox.com• POST http://matteo.vaccari.name/blog• PUT http://matteo.vaccari.name/blog/123 http://matteo.vaccari.name/blog/2007-05• DELETE
  9. 9. REST and CRUD• GET • index, show, new, edit • SELECT• POST • create • INSERT• PUT • update • UPDATE• DELETE • destroy • DELETE
  10. 10. Embrace REST RPCcart: show, add_item, remove_item, add_coupon, removecoupon, increase_quantity, decrease_quantity RESTcart: show, create, update, destroycart_items: show, create, update, destroycart_coupons: show, create, update, destroy Move variation from verbs to nouns
  11. 11. Embrace RESTScott Raymond, Refactoring to REST, 2006/6/20Before refactoring, IconBuffet had 10 controllers and76 actions. Now, without adding or removing anyfeatures, IconBuffet has 13 controllers and 58actions.There are seven standard Rails actions: index, new,create, show, edit, update, and destroy. Everythingelse—oddball actions—are usually a clue that you’redoing RPC.
  12. 12. Connect the dots...• REST thinking reduces complex domains to CRUDs• Rails makes it easy to do CRUDs• Rails makes it easy to do REST➡ €€€ !!!
  13. 13. Idea #1: embrace OOP
  14. 14. Boolean configurations bring IFsSTORES_CONFIGURATION[:foo] = { :tracking_email_enabled => true, :simple_agency_tracking_enabled => true, :remote_user_login => false, ...} if current_store_config[:tracking_email_enabled] do_something else do_something_else end
  15. 15. OK. Nessuno te l’ha detto finora ma...Aggiungere IF è il male.
  16. 16. Francesco CirilloCOMODO ≠ EFFICACE http://www.antiifcampaign.com/
  17. 17. http://pierg.wordpress.com/2009/08/05/anti-if-campaign/
  18. 18. STORES_CONFIGURATION[:foo] = { :tracking_email_enabled => true, :simple_agency_tracking_enabled => true, :remote_user_login => false, ...}if current_store_config[:tracking_email_enabled] do_somethingelse STORES[:foo] = Store.new( do_something_else :email_tracker => EmailTracker.new,end :agency_tracker => SimpleAgencyTracker.new ... ) STORES[:bar] = Store.new( :tracking_email => NullEmailTracker.new, ... ) current_store.email_tracker.do_something
  19. 19. Fat modelsclass Product < ActiveRecord::Base # ... 363 lines ...end
  20. 20. class Product < ActiveRecord::Base # ... named_scope :full_text_search, lambda { |keywords| if keywords.blank? { :conditions => "0 = 1"} else keywords = whitelist_characters_for_search(keywords) { :conditions => [ " products.code LIKE ? or match (product_translations.name_actual) against (? in boolean mode or product_translations.name_actual regexp ? ", keywords + %, expand_search_aliases(keywords), prepare_for_regexp_search(k ], :joins => join_with_translations_table } end } private def self.whitelist_characters_for_search(keywords) # ... end
  21. 21. end } private def self.whitelist_characters_for_search(keywords) # ... end def self.prepare_for_regexp_search(keywords_string) # ... end def self.expand_search_aliases(keywords_string) # ... end def self.expand_alias keywords, key, value # ... end def self.join_with_translations_table # ... end # ...end
  22. 22. Cure: use compositionclass Product < ActiveRecord::Base # ... named_scope :full_text_search, lambda { |keywords| FullTextSearch.new(keywords).to_scope } class FullTextSearch # ...end def to_scope { :conditions => ... } end private def whitelist_characters_for_search # ... end # ... end
  23. 23. Eventually... class Product < ActiveRecord::Base extend ProductFinders include ProductCategoryMethods include TranslationEnumerator include ProductImages endVedi Rails Antipatterns by Pytel & Saleh
  24. 24. Tediumit "displays information for a given user" do Factory.create(:user, :id => "1234", :first_name => "Arthur") get "/users/display?id=1234" assert_select "table" do assert_select "td#user_first_name", "Arthur" endend Red <table> <tr class="even"> <td><strong>First Name</strong></td> <td id="user_first_name"><%= @user.first_name %></td> </tr> </table> Green
  25. 25. it "displays information for a given user" do Factory.create(:user, :id => "1234", :first_name => "Arthur", :last_name => "Fonzarelli") get "/users/display?id=1234" assert_select "table" do assert_select "td#user_first_name", "Arthur" assert_select "td#user_last_name", "Fonzarelli" endend Red<table> <tr class="even"> <td><strong>First Name</strong></td> <td id="user_first_name"><%= @user.first_name %></td> </tr> <tr class="odd"> <td><strong>Last Name</strong></td> <td id="user_last_name"><%= @user.last_name %></td> </tr></table> Green
  26. 26. it "displays information for a given user" do Factory.create(:user, :id => "1234", :first_name => "Arthur", :last_name => "Fonzarelli", :email => "fonzie@happydays.com") ! ! get "/users/display?id=1234" g h assert_select "table" do a assert_select "td#user_first_name", "Arthur" a assert_select "td#user_last_name", "Fonzarelli" a assert_select "td#user_email", "fonzie@happydays.com" a end a end a a <table> Red a <tr class="even">a <td><strong>First Name</strong></td> <td id="user_first_name"><%= @user.first_name %></td> </tr> Green <tr class="odd"> <td><strong>Last Name</strong></td> Red <td id="user_last_name"><%= @user.last_name %></td> </tr> Green <tr class="even"> Red <td><strong>Email</strong></td> <td id="user_email"><%= @user.email %></td> </tr> Green </table>
  27. 27. Useless IDs!!! Duplication!!!<table> <tr class="even"> <td><strong>First Name</strong></td> <td id="user_first_name"><%= @user.first_name %></td> </tr> <tr class="odd"> <td><strong>Last Name</strong></td> <td id="user_last_name"><%= @user.last_name %></td> </tr> <tr class="even"> <td><strong>Email</strong></td> <td id="user_email"><%= @user.email %></td> </tr></table>
  28. 28. it "displays information for a given user" do Factory.create(:user, :id => "1234", :first_name => "Arthur", :last_name => "Fonzarelli", :email => "fonzie@happydays.com", :street => "123 foob :city => "Milwaukee", :zip => "99911", :phone => "1-234-5678") get "/users/display?id=1234" assert_select "table" do assert_select "td#user_first_name", "Arthur" assert_select "td#user_last_name", "Fonzarelli" assert_select "td#user_email", "fonzie@happydays.com" assert_select "td#user_street", "123 foobar lane"No objects emerge. assert_select "td#user_city", "Milwaukee" assert_select "td#user_zip", "99911" assert_select "td#user_phone", "1-234-5678" No abstraction. end end No creativity. Duplication!!! How sad. Boredom!!!
  29. 29. The original definition of TDD says:1.Quickly add a test.2.Run all tests and see the new one fail.3.Make a little change.4.Run all tests and see them all succeed.5.Refactor to remove duplication.Kent Beck, Test Driven Development: By ExampleNot "refactor at will"!
  30. 30. <table> <tr class="even"> <td><strong>First Name</strong></td> <td id="user_first_name"><%= @user.first_name %></td> </tr> <tr class="odd"> <td><strong>Last Name</strong></td> <td id="user_last_name"><%= @user.last_name %></td> </tr> <tr class="even"> <td><strong>Email</strong></td> <td id="user_email"><%= @user.email %></td> </tr></table> <table> <%= admin_table_row "even", "First Name", @user.first_name %> <%= admin_table_row "odd", "Last Name", @user.last_name %> <%= admin_table_row "even", "Email", @user.email %> </table> <%= AdminTable.new(@user.attributes_for_administration).to_html %>
  31. 31. <%= AdminTable.new(@user.attributes_for_administration).to_html%>it "produces an html table" do model = [["Label 0", "value 0"]] expected = <<-EOF <table> <tr class="even"> <td><strong>Label 0</strong></td> <td>value 0</td> </tr> </table> EOF assert_dom_equal expected, AdminTable.new(model).to_htmlend
  32. 32. A nonobvious conclusionSolving a slightly more general problem than strictly necessary is often easier, simpler and cleaner! See also George Pólya, How to solve it
  33. 33. Use every weapon• Use objects in place of IFs• Split models, delegate to objects and modules• Refactor views! Use helpers everywhere• Use plugins• Develop your own DSL• Think general! Not specific! Abstract!• Have fun!!!
  34. 34. Want to know more?
  35. 35. Want to know more? The Clean Code Talks - Dont Look For Things! Miško Hevery The Clean Code Talks #2
  36. 36. Want to know more? Read chapter one!
  37. 37. Want to know more? http://www.antiifcampaign.com/
  38. 38. Want to know more? http://matteo.vaccari.name/blog/ matteo.vaccari@xpeppers.com twitter: @xpmatteoThis presentation can be downloaded from http://slideshare.net/xpmatteo
  39. 39. Grazie dell’attenzione! Extreme Programming:development & mentoring

×