Solid design for Rails

3,753 views
3,577 views

Published on

My presentation at the Italian Ruby Day

Published in: Technology
1 Comment
7 Likes
Statistics
Notes
No Downloads
Views
Total views
3,753
On SlideShare
0
From Embeds
0
Number of Embeds
1,408
Actions
Shares
0
Downloads
23
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

×