Solid design for Rails
Upcoming SlideShare
Loading in...5
×
 

Solid design for Rails

on

  • 3,539 views

My presentation at the Italian Ruby Day

My presentation at the Italian Ruby Day

Statistics

Views

Total Views
3,539
Views on SlideShare
2,160
Embed Views
1,379

Actions

Likes
6
Downloads
21
Comments
1

6 Embeds 1,379

http://matteo.vaccari.name 1351
http://lanyrd.com 20
url_unknown 3
http://www.slideshare.net 2
http://paper.li 2
http://webcache.googleusercontent.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

11 of 1

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • 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
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    Solid design for Rails Solid design for Rails Presentation Transcript

    • Solid Designfor Rails applications Matteo Vaccari matteo.vaccari@xpeppers.com www.xpeppers.com Italian Ruby Day, 2011/06/10 (cc) Some rights reserved
    • 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
    • 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
    • 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
    • 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://matteo.vaccari.name/blog/123 http://matteo.vaccari.name/blog/2007-05• DELETE
    • REST and CRUD• GET • index, show, new, edit • SELECT• POST • create • INSERT• PUT • update • UPDATE• DELETE • destroy • DELETE
    • 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
    • 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.
    • Connect the dots...• REST thinking reduces complex domains to CRUDs• Rails makes it easy to do CRUDs• Rails makes it easy to do REST➡ €€€ !!!
    • Idea #1: embrace OOP
    • 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
    • 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, :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
    • Fat modelsclass Product < ActiveRecord::Base # ... 363 lines ...end
    • 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
    • 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
    • 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
    • Eventually... class Product < ActiveRecord::Base extend ProductFinders include ProductCategoryMethods include TranslationEnumerator include ProductImages endVedi Rails Antipatterns by Pytel & Saleh
    • 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
    • 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
    • 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>
    • 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>
    • 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!!!
    • 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"!
    • <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 %>
    • <%= 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
    • 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
    • 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!!!
    • 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 presentation can be downloaded from http://slideshare.net/xpmatteo
    • Grazie dell’attenzione! Extreme Programming:development & mentoring