Download - Solid design for Rails
![Page 1: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/1.jpg)
Solid Design for Rails applications
Matteo Vaccari
Italian Ruby Day, 2011/06/10(cc) Some rights reserved
![Page 2: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/2.jpg)
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
![Page 3: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/3.jpg)
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
![Page 4: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/4.jpg)
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
![Page 5: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/5.jpg)
What’s the problem?
• Maintainability
• Long-term maintainability
• But, in general, maintainability
![Page 6: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/6.jpg)
In a good project...
• The cost of delivering features decreases over time
![Page 7: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/7.jpg)
Idea #0: embrace REST
![Page 8: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/8.jpg)
Verbs and nouns
• GET
• POST
• PUT
• DELETE
mailto:[email protected]://matteo.vaccari.name/bloghttp://matteo.vaccari.name/blog/123http://matteo.vaccari.name/blog/2007-05
![Page 9: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/9.jpg)
REST and CRUD
• GET
• POST
• PUT
• DELETE
• index, show, new, edit
• create
• update
• destroy
• SELECT
• INSERT
• UPDATE
• DELETE
![Page 10: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/10.jpg)
RPCcart: show, add_item, remove_item, add_coupon, remove coupon, 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 REST
![Page 11: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/11.jpg)
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.
![Page 12: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/12.jpg)
Connect the dots...
• REST thinking reduces complex domains to CRUDs
• Rails makes it easy to do CRUDs
• Rails makes it easy to do REST
➡ €€€ !!!
![Page 13: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/13.jpg)
Idea #1: embrace OOP
![Page 14: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/14.jpg)
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_somethingelse do_something_elseend
![Page 15: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/15.jpg)
OK. Nessuno te l’ha detto finora ma...
Aggiungere IF è il male.
![Page 16: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/16.jpg)
COMODO ≠ EFFICACEhttp://www.antiifcampaign.com/
Francesco Cirillo
![Page 17: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/17.jpg)
http://pierg.wordpress.com/2009/08/05/anti-if-campaign/
![Page 18: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/18.jpg)
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 do_something_elseend
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
![Page 19: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/19.jpg)
Fat models
class Product < ActiveRecord::Base # ... 363 lines ...end
![Page 20: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/20.jpg)
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(keywords) ], :joins => join_with_translations_table } 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
![Page 21: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/21.jpg)
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(keywords) ], :joins => join_with_translations_table } 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
![Page 22: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/22.jpg)
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
![Page 23: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/23.jpg)
Eventually...
class Product < ActiveRecord::Base extend ProductFinders include ProductCategoryMethods include TranslationEnumerator include ProductImages end
Vedi Rails Antipatterns by Pytel & Saleh
![Page 24: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/24.jpg)
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
<table> <tr class="even"> <td><strong>First Name</strong></td> <td id="user_first_name"><%= @user.first_name %></td> </tr></table>
Red
Green
![Page 25: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/25.jpg)
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
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>
![Page 26: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/26.jpg)
<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 => "[email protected]") 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", "[email protected]" endend
Red
Green
Red
Green
Red
Green
aaaaaaaaag
h!!
![Page 27: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/27.jpg)
<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!!!
![Page 28: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/28.jpg)
it "displays information for a given user" do Factory.create(:user, :id => "1234", :first_name => "Arthur", :last_name => "Fonzarelli", :email => "[email protected]", :street => "123 foobar lane", ) :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", "[email protected]" 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" endend
Duplication!!!Boredom!!!
No objects emerge.
No abstraction.
No creativity.
How sad.
![Page 29: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/29.jpg)
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"!
![Page 30: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/30.jpg)
<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%>
![Page 31: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/31.jpg)
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
<%= AdminTable.new(@user.attributes_for_administration).to_html%>
![Page 32: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/32.jpg)
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
![Page 33: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/33.jpg)
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!!!
![Page 34: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/34.jpg)
Want to know more?
![Page 35: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/35.jpg)
Want to know more?
The Clean Code Talks #2
The Clean Code Talks - Don't Look For Things!
Miško Hevery
![Page 36: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/36.jpg)
Want to know more?
Read chapter one!
![Page 37: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/37.jpg)
Want to know more?
http://www.antiifcampaign.com/
![Page 38: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/38.jpg)
Want to know more?
http://matteo.vaccari.name/blog/
[email protected]: @xpmatteo
This presentation can be downloaded from http://slideshare.net/xpmatteo
![Page 39: Solid design for Rails](https://reader038.vdocument.in/reader038/viewer/2022103000/554a1215b4c9055c598b4bc2/html5/thumbnails/39.jpg)
Extreme Programming:development & mentoring
Grazie dell’attenzione!