$30 off During Our Annual Pro Sale. View Details »

Solid design for Rails applications

Solid design for Rails applications

My presentation at the Italian Ruby Day 2011

Matteo Vaccari

June 10, 2011
Tweet

More Decks by Matteo Vaccari

Other Decks in Technology

Transcript

  1. Solid Design for Rails applications Matteo Vaccari matteo.vaccari@xpeppers.com www.xpeppers.com Italian

    Ruby Day, 2011/06/10 (cc) Some rights reserved
  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. There are a few things I look for that are

    good predictors of whether a project is in good shape. Once and only once ... Lots of little pieces - Good code invariably has small methods and small objects. Only by factoring the system into many small pieces of state and function can you hope to satisfy the “once and only once” rule. ... Replacing objects - Good style leads to easily replaceable objects. In a really good system, every time the user says “I want to do this radically different thing,” the developer says, “Oh, I’ll have to make a new kind of X and plug it in.” ... Kent Beck – Smalltalk Best Practice Patterns
  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. What’s the problem? • Maintainability • Long-term maintainability • But,

    in general, maintainability
  6. In a good project... • The cost of delivering features

    decreases over time
  7. Idea #0: embrace REST

  8. Verbs and nouns • GET • POST • PUT •

    DELETE mailto:vaccari@pobox.com http://matteo.vaccari.name/blog http://matteo.vaccari.name/blog/123 http://matteo.vaccari.name/blog/2007-05
  9. REST and CRUD • GET • POST • PUT •

    DELETE • index, show, new, edit • create • update • destroy • SELECT • INSERT • UPDATE • DELETE
  10. RPC cart: show, add_item, remove_item, add_coupon, remove coupon, increase_quantity, decrease_quantity

    REST cart: show, create, update, destroy cart_items: show, create, update, destroy cart_coupons: show, create, update, destroy Move variation from verbs to nouns Embrace REST
  11. Embrace REST Scott Raymond, Refactoring to REST, 2006/6/20 Before refactoring,

    IconBuffet had 10 controllers and 76 actions. Now, without adding or removing any features, IconBuffet has 13 controllers and 58 actions. There are seven standard Rails actions: index, new, create, show, edit, update, and destroy. Everything else—oddball actions—are usually a clue that you’re doing RPC.
  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. Idea #1: embrace OOP

  14. Boolean configurations bring IFs STORES_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. OK. Nessuno te l’ha detto finora ma... Aggiungere IF è

    il male.
  16. COMODO ≠ EFFICACE http://www.antiifcampaign.com/ Francesco Cirillo

  17. http://pierg.wordpress.com/2009/08/05/anti-if-campaign/

  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_something else do_something_else end STORES[:foo] = Store.new( :email_tracker => EmailTracker.new, :agency_tracker => SimpleAgencyTracker.new ... ) STORES[:bar] = Store.new( :tracking_email => NullEmailTracker.new, ... ) current_store.email_tracker.do_something
  19. Fat models class Product < ActiveRecord::Base # ... 363 lines

    ... end
  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. 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. class Product < ActiveRecord::Base # ... named_scope :full_text_search, lambda {

    |keywords| FullTextSearch.new(keywords).to_scope } # ... end class FullTextSearch def to_scope { :conditions => ... } end private def whitelist_characters_for_search # ... end # ... end Cure: use composition
  23. Eventually... class Product < ActiveRecord::Base extend ProductFinders include ProductCategoryMethods include

    TranslationEnumerator include ProductImages end Vedi Rails Antipatterns by Pytel & Saleh
  24. Tedium it "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" end end <table> <tr class="even"> <td><strong>First Name</strong></td> <td id="user_first_name"><%= @user.first_name %></td> </tr> </table> Red Green
  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" end end Red Green <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>
  26. <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> 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" 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" end end Red Green Red Green Red Green aaaaaaaaagh!!
  27. <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> Duplication!!! Useless IDs!!!
  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" assert_select "td#user_city", "Milwaukee" assert_select "td#user_zip", "99911" assert_select "td#user_phone", "1-234-5678" end end Duplication!!! Boredom!!! No objects emerge. No abstraction. No creativity. How sad.
  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 Example Not "refactor at will"!
  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. 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_html end <%= AdminTable.new(@user.attributes_for_administration).to_html %>
  32. A nonobvious conclusion Solving a slightly more general problem than

    strictly necessary is often easier, simpler and cleaner! See also George Pólya, How to solve it
  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. Want to know more?

  35. Want to know more? The Clean Code Talks #2 The

    Clean Code Talks - Don't Look For Things! Miško Hevery
  36. Want to know more? Read chapter one!

  37. Want to know more? http://www.antiifcampaign.com/

  38. Want to know more? http://matteo.vaccari.name/blog/ matteo.vaccari@xpeppers.com twitter: @xpmatteo This presentation

    can be downloaded from http://slideshare.net/xpmatteo
  39. Extreme Programming: development & mentoring Grazie dell’attenzione!