Rails Best Practices
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

Rails Best Practices

on

  • 109,680 views

see http://ihower.tw/blog/archives/3075 , this's my talk at http://kungfurails.com and http://ruby.tw

see http://ihower.tw/blog/archives/3075 , this's my talk at http://kungfurails.com and http://ruby.tw

Statistics

Views

Total Views
109,680
Views on SlideShare
97,980
Embed Views
11,700

Actions

Likes
330
Downloads
2,788
Comments
22

94 Embeds 11,700

http://ihower.tw 5556
http://masaiwasaki.tumblr.com 3420
http://www.slideshare.net 505
http://burm.net 494
http://knowledgebase.lister.net 197
http://ihower.idv.tw 196
http://kimoto.hatenablog.com 152
http://www.railsmine.net 107
http://wecanooo.tistory.com 93
http://webcache.googleusercontent.com 76
http://rubyrailsandwindows.blogspot.com 71
http://www.neevtech.com 69
http://www.caiwangqin.com 68
http://localhost 67
http://venture-lab.org 52
http://www.stayonhere.com 40
http://lab.wordstory.net 36
http://robdoan.heroku.com 35
http://supriya-surve.blogspot.com 34
http://railsandruby.blogspot.com 34
http://feeds.feedburner.com 31
http://stager.novoed.com 31
http://supriya-surve.blogspot.in 30
http://paper.li 27
http://www.twylah.com 27
http://neevtech.com 25
http://www.supriyasurve.com 23
http://rubyrailsandwindows.blogspot.in 22
https://twitter.com 15
http://rorlab.org 14
http://nikolaifedorovjobs.blogspot.com 14
http://feedly.com 8
http://rubyrailsandwindows.blogspot.com.br 8
http://www.linkedin.com 6
http://safe.txmblr.com 5
http://rubyrailsandwindows.blogspot.com.au 5
http://nikolaifedorovjobs.blogspot.ru 5
http://s.deeeki.com 5
http://aptcape.tumblr.com 5
http://blondelle-gardner.tumblr.com 5
http://rubyrailsandwindows.blogspot.de 4
http://comprendrelacommunicationintegree.fr 4
http://dothingssimple.wordpress.com 4
http://translate.googleusercontent.com 3
http://seekr-artemis.heroku.com 3
http://caiwangqin.com 3
http://static.slidesharecdn.com 3
http://bubonicpestilence.ru 3
http://blog.caiwangqin.com 3
http://slidesync.herokuapp.com 2
More...

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

15 of 22 Post a comment

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • This presentation is very helpful for me. circular saw reviews
    Are you sure you want to
    Your message goes here
    Processing…
  • good slides
    Are you sure you want to
    Your message goes here
    Processing…
  • Great slides. Thanks for sharing.

    Regards
    http://www.clickandsendparcel.com
    Are you sure you want to
    Your message goes here
    Processing…
  • Perfecto!
    Are you sure you want to
    Your message goes here
    Processing…
  • Nice
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Rails Best Practices Presentation Transcript

  • 1. Rails Best Practices ihower@gmail.com As this slide writing, the current Rails version is 2.3.4
  • 2. Who am I ? • a.k.a. ihower • http://ihower.tw • http://twitter.com/ihower • http://github.com/ihower • (Hsinchu, Taiwan) • ( ) • http://handlino.com • http://registrano.com
  • 3. Ruby Taiwan http://ruby.tw
  • 4. Agenda • Concept: What’s good code? • Move Code from Controller to Model • RESTful best practices • Model best practices • Controller best practices • View best practices
  • 5. Warning! you should have testing before modify!
  • 6. Best Practice Lesson 0: Concepts
  • 7. Why best practices? • Large & complicated application • Team & different coding style
  • 8. Your code become... • (Rigidity) • (Fragility) • (Immobility) • (Viscosity) • (Needless Complexity) • (Needless Repetition) • (Opacity) Agile Software Development: Principles, Patterns, and Practices
  • 9. We need good code:
  • 10. What’s Good code? • Readability • Flexibility • Effective • Maintainability • Consistency • Testability
  • 11. So, What we can do?
  • 12. Best Practice Lesson 1: Move code from Controller to Model action code 15 http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model
  • 13. Before 1.Move finder to named_scope class PostsController < ApplicationController def index @public_posts = Post.find(:all, :conditions => { :state => 'public' }, :limit => 10, :order => 'created_at desc') @draft_posts = Post.find(:all, :conditions => { :state => 'draft' }, :limit => 10, :order => 'created_at desc') end end
  • 14. After 1.Move finder to named_scope class UsersController < ApplicationController def index @published_post = Post.published @draft_post = Post.draft end end class Post < ActiveRecord::Base named_scope :published, :conditions => { :state => 'published' }, :limit => 10, :order => 'created_at desc') named_scope :draft, :conditions => { :state => 'draft' }, :limit => 10, :order => 'created_at desc') end
  • 15. Before 2. Use model association class PostsController < ApplicationController def create @post = Post.new(params[:post]) @post.user_id = current_user.id @post.save end end
  • 16. After 2. Use model association class PostsController < ApplicationController def create @post = current_user.posts.build(params[:post]) @post.save end end class User < ActiveRecord::Base has_many :posts end
  • 17. 3. Use scope access Before class PostsController < ApplicationController def edit @post = Post.find(params[:id) if @post.current_user != current_user flash[:warning] = 'Access denied' redirect_to posts_url end end end
  • 18. 3. Use scope access After class PostsController < ApplicationController def edit # raise RecordNotFound exception (404 error) if not found @post = current_user.posts.find(params[:id) end end
  • 19. Before 4. Add model virtual attribute <% form_for @user do |f| %> <%= text_filed_tag :full_name %> <% end %> class UsersController < ApplicationController def create @user = User.new(params[:user) @user.first_name = params[:full_name].split(' ', 2).first @user.last_name = params[:full_name].split(' ', 2).last @user.save end end
  • 20. After 4. Add model virtual attribute class User < ActiveRecord::Base def full_name [first_name, last_name].join(' ') end def full_name=(name) split = name.split(' ', 2) self.first_name = split.first self.last_name = split.last end end example code from http://railscasts.com/episodes/16-virtual-attributes
  • 21. After <% form_for @user do |f| %> <%= f.text_field :full_name %> <% end %> class UsersController < ApplicationController def create @user = User.create(params[:user) end end example code from http://railscasts.com/episodes/16-virtual-attributes
  • 22. 5. Use model callback Before <% form_for @post do |f| %> <%= f.text_field :content %> <%= check_box_tag 'auto_tagging' %> <% end %> class PostController < ApplicationController def create @post = Post.new(params[:post]) if params[:auto_tagging] == '1' @post.tags = AsiaSearch.generate_tags(@post.content) else @post.tags = "" end @post.save end end
  • 23. After 5. Use model callback class Post < ActiveRecord::Base attr_accessor :auto_tagging before_save :generate_taggings private def generate_taggings return unless auto_tagging == '1' self.tags = Asia.search(self.content) end end
  • 24. After <% form_for :note, ... do |f| %> <%= f.text_field :content %> <%= f.check_box :auto_tagging %> <% end class PostController < ApplicationController def create @post = Post.new(params[:post]) @post.save end end
  • 25. 6. Replace Complex Creation Before with Factory Method class InvoiceController < ApplicationController def create @invoice = Invoice.new(params[:invoice]) @invoice.address = current_user.address @invoice.phone = current_user.phone @invoice.vip = ( @invoice.amount > 1000 ) if Time.now.day > 15 @invoice.delivery_time = Time.now + 2.month else @invoice.delivery_time = Time.now + 1.month end @invoice.save end end
  • 26. 6. Replace Complex Creation After with Factory Method class Invoice < ActiveRecord::Base def self.new_by_user(params, user) invoice = self.new(params) invoice.address = user.address invoice.phone = user.phone invoice.vip = ( invoice.amount > 1000 ) if Time.now.day > 15 invoice.delivery_time = Time.now + 2.month else invoice.delivery_time = Time.now + 1.month end end end
  • 27. After class InvoiceController < ApplicationController def create @invoice = Invoice.new_by_user(params[:invoice], current_user) @invoice.save end end
  • 28. 7. Move Model Logic into the Before Model class PostController < ApplicationController def publish @post = Post.find(params[:id]) @post.update_attribute(:is_published, true) @post.approved_by = current_user if @post.create_at > Time.now - 7.days @post.popular = 100 else @post.popular = 0 end redirect_to post_url(@post) end end
  • 29. 7. Move Model Logic into the After Model class Post < ActiveRecord::Base def publish self.is_published = true self.approved_by = current_user if self.create_at > Time.now-7.days self.popular = 100 else self.popular = 0 end end end
  • 30. After class PostController < ApplicationController def publish @post = Post.find(params[:id]) @post.publish redirect_to post_url(@post) end end
  • 31. 8. model.collection_model_ids (many-to-many) class User < ActiveRecord::Base has_many :user_role_relationship has_many :roles, :through => :user_role_relationship end class UserRoleRelationship < ActiveRecord::Base belongs_to :user belongs_to :role end class Role < ActiveRecord::Base end
  • 32. Before <% form_for @user do |f| %> <%= f.text_field :email %> <% for role in Role.all %> <%= check_box_tag 'role_id[]', role.id, @user.roles.include?(role) %> <%= role.name %> <% end %> <% end %> class User < ApplicationController def update @user = User.find(params[:id]) if @user.update_attributes(params[:user]) @user.roles.delete_all (params[:role_id] || []).each { |i| @user.roles << Role.find(i) } end end end
  • 33. After <% form_for @user do |f| %> <% for role in Role.all %> <%= check_box_tag 'user[role_ids][]', role.id, @user.roles.include?(role) <%= role.name %> <% end %> <%= hidden_field_tag 'user[role_ids][]', '' %> <% end %> class User < ApplicationController def update @user = User.find(params[:id]) @user.update_attributes(params[:user]) # @user.role_ids = params[:user][:role_ids] end end
  • 34. 9. Nested Model Forms (one-to-one) Before class Product < ActiveRecord::Base has_one :detail end class Detail < ActiveRecord::Base belongs_to :product end <% form_for :product do |f| %> <%= f.text_field :title %> <% fields_for :detail do |detail| %> <%= detail.text_field :manufacturer %> <% end %> <% end %>
  • 35. Before class Product < ApplicationController def create @product = Product.new(params[:product]) @details = Detail.new(params[:detail]) Product.transaction do @product.save! @details.product = @product @details.save! end end end example code from Agile Web Development with Rails 3rd.
  • 36. 9. Nested Model Forms (one-to-one) After Rails 2.3 new feature class Product < ActiveRecord::Base has_one :detail accepts_nested_attributes_for :detail end <% form_for :product do |f| %> <%= f.text_field :title %> <% f.fields_for :detail do |detail| %> <%= detail.text_field :manufacturer %> <% end %> <% end
  • 37. After class Product < ApplicationController def create @product = Product.new(params[:product]) @product.save end end
  • 38. 10. Nested Model Forms (one-to-many) class Project < ActiveRecord::Base has_many :tasks accepts_nested_attributes_for :tasks end class Task < ActiveRecord::Base belongs_to :project end <% form_for @project do |f| %> <%= f.text_field :name %> <% f.fields_for :tasks do |tasks_form| %> <%= tasks_form.text_field :name %> <% end %> <% end %>
  • 39. Nested Model Forms before Rails 2.3 ? • Ryan Bates’s series of railscasts on complex forms • http://railscasts.com/episodes/75-complex-forms-part-3 • Recipe 13 in Advanced Rails Recipes book
  • 40. Best Practice Lesson 2: RESTful RESTful conventions
  • 41. Why RESTful? RESTful help you to organize/name controllers, routes and actions in standardization way
  • 42. Before class EventsController < ApplicationController def index def white_member_list def watch_list def feeds end end end end def show def black_member_list def add_favorite def add_comment end end end end def create def deny_user def invite def show_comment end end end end def update def allow_user def join def destroy_comment end end end end def destroy def edit_managers def leave def edit_comment end end end end def approve_comment def set_user_as_manager end end def set_user_as_member end end
  • 43. After class EventsController < ApplicationController def index; end def show; end end class CommentsControlers < ApplicationController def index; end def create; end def destroy; end end def FavoriteControllers < ApplicationController def create; end def destroy; end end class EventMembershipsControlers < ApplicationController def create; end def destroy; end end
  • 44. Before 1. Overuse route customizations map.resources :posts, :member => { :comments => :get, :create_comment => :post, :update_comment => :post, :delete_comment => :post }
  • 45. After 1. Overuse route customizations Find another resources map.resources :posts do |post| post.resources :comments end
  • 46. Suppose we has a event model... class Event < ActiveRecord::Base has_many :attendee has_one :map has_many :memberships has_many :users, :through => :memberships end
  • 47. Can you answer how to design your resources ? • manage event attendees (one-to-many) • manage event map (one-to-one) • manage event memberships (many-to-many) • operate event state: open or closed • search events • sorting events • event admin interface
  • 48. Learn RESTful design my slide about restful: http://www.slideshare.net/ihower/practical-rails2-350619
  • 49. Before 2. Needless deep nesting : Never more than one level map.resources :posts do |post| post.resources :comments do |comment| comment.resources :favorites end end <%= link_to post_comment_favorite_path(@post, @comment, @favorite) %>
  • 50. After 2. Needless deep nesting : Never more than one level map.resources :posts do |post| post.resources :comments end map.resources :comments do |comment| comment.resources :favorites end <%= link_to comment_favorite_path(@comment, @favorite) %>
  • 51. Before 3. Not use default route map.resources :posts, :member => { :push => :post } map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format'
  • 52. After 3. Not use default route map.resources :posts, :member => { :push => :post } #map.connect ':controller/:action/:id' #map.connect ':controller/:action/:id.:format' map.connect 'special/:action/:id', :controller => 'special'
  • 53. Best Practice Lesson 3: Model
  • 54. Before 1. Keep Finders on Their Own Model class Post < ActiveRecord::Base has_many :comments def find_valid_comments self.comment.find(:all, :conditions => { :is_spam => false }, :limit => 10) end end class Comment < ActiveRecord::Base belongs_to :post end class CommentsController < ApplicationController def index @comments = @post.find_valid_comments end end
  • 55. After 1. Keep Finders on Their Own Model class Post < ActiveRecord::Base has_many :comments end class Comment < ActiveRecord::Base belongs_to :post named_scope :only_valid, :conditions => { :is_spam => false } named_scope :limit, lambda { |size| { :limit => size } } end class CommentsController < ApplicationController def index @comments = @post.comments.only_valid.limit(10) end end
  • 56. Before 2. Love named_scope class PostController < ApplicationController def search conditions = { :title => "%#{params[:title]}%" } if params[:title] conditions.merge!{ :content => "%#{params[:content]}%" } if params[:content] case params[:order] when "title" : order = "title desc" when "created_at" : order = "created_at" end if params[:is_published] conditions.merge!{ :is_published => true } end @posts = Post.find(:all, :conditions => conditions, :order => order, :limit => params[:limit]) end end example code from Rails Antipatterns book
  • 57. After 2. Love named_scope class Post < ActiveRecord::Base named_scope :matching, lambda { |column, value| return {} if value.blank? { :conditions => ["#{column} like ?", "%#{value}%"] } } named_scope :order, lambda { |order| { :order => case order when "title" : "title desc" when "created_at" : "created_at" end } } end
  • 58. After class PostController < ApplicationController def search @posts = Post.matching(:title, params[:title]) .matching(:content, params[:content]) .order(params[:order]) end end
  • 59. Before 3. the Law of Demeter class Invoice < ActiveRecord::Base belongs_to :user end <%= @invoice.user.name %> <%= @invoice.user.address %> <%= @invoice.user.cellphone %>
  • 60. After 3. the Law of Demeter class Invoice < ActiveRecord::Base belongs_to :user delegate :name, :address, :cellphone, :to => :user, :prefix => true end <%= @invoice.user_name %> <%= @invoice.user_address %> <%= @invoice.user_cellphone %>
  • 61. 4. DRY: Metaprogramming Before class Post < ActiveRecord::Base validate_inclusion_of :status, :in => ['draft', 'published', 'spam'] def self.all_draft find(:all, :conditions => { :status => 'draft' } end def self.all_published find(:all, :conditions => { :status => 'published' } end def self.all_spam find(:all, :conditions => { :status => 'spam' } end def draft? self.stats == 'draft' end def published? self.stats == 'published' end def spam? self.stats == 'spam' end end
  • 62. 4. DRY: Metaprogramming After class Post < ActiveRecord::Base STATUSES = ['draft', 'published', 'spam'] validate_inclusion_of :status, :in => STATUSES class << self STATUSES.each do |status_name| define_method "all_#{status}" do find(:all, :conditions => { :status => status_name } end end end STATUSES.each do |status_name| define_method "#{status_name}?" do self.status == status_name end end end
  • 63. Breaking Up Models Model
  • 64. Before 5. Extract into Module class User < ActiveRecord::Base validates_presence_of :cellphone before_save :parse_cellphone def parse_cellphone # do something end end
  • 65. After # /lib/has_cellphone.rb module HasCellphone def self.included(base) base.validates_presence_of :cellphone base.before_save :parse_cellphone base.send(:include,InstanceMethods) base.send(:extend, ClassMethods) end module InstanceMethods def parse_cellphone # do something end end module ClassMethods end end
  • 66. After class User < ActiveRecord::Base include HasCellphone end
  • 67. Before 6. Extract to composed class # == Schema Information # address_city :string(255) # address_street :string(255) class Customer < ActiveRecord::Base def adddress_close_to?(other_customer) address_city == other_customer.address_city end def address_equal(other_customer) address_street == other_customer.address_street && address_city == other_customer.address_city end end
  • 68. 6. Extract to composed class After (value object) class Customer < ActiveRecord::Base composed_of :address, :mapping => [ %w(address_street street), %w(address_city city) ] end class Address attr_reader :street, :city def initialize(street, city) @street, @city = street, city end def close_to?(other_address) city == other_address.city end def ==(other_address) city == other_address.city && street == other_address.street end end example code from Agile Web Development with Rails 3rd.
  • 69. Before 7. Use Observer class Project < ActiveRecord::Base after_create :send_create_notifications private def send_create_notifications self.members.each do |member| ProjectNotifier.deliver_notification(self, member) end end end
  • 70. After 7. Use Observer class Project < ActiveRecord::Base # nothing here end # app/observers/project_notification_observer.rb class ProjectNotificationObserver < ActiveRecord::Observer observe Project def after_create(project) project.members.each do |member| ProjectMailer.deliver_notice(project, member) end end end
  • 71. Best Practice Lesson 4: Migration
  • 72. Before 1. Isolating Seed Data class CreateRoles < ActiveRecord::Migration def self.up create_table "roles", :force => true do |t| t.string :name end ["admin", "author", "editor","account"].each do |name| Role.create!(:name => name) end end def self.down drop_table "roles" end end
  • 73. After 1. Isolating Seed Data # /db/seeds.rb (Rails 2.3.4) ["admin", "author", "editor","account"].each do |name| Role.create!(:name => name) end rake db:seed
  • 74. After # /lib/tasks/dev.rake (before Rails 2.3.4) namespace :dev do desc "Setup seed data" task :setup => :environment do ["admin", "author", "editor","account"].each do |name| Role.create!(:name => name) end end end rake dev:setup
  • 75. Before 2. Always add DB index class CreateComments < ActiveRecord::Migration def self.up create_table "comments", :force => true do |t| t.string :content t.integer :post_id t.integer :user_id end end def self.down drop_table "comments" end end
  • 76. After 2. Always add DB index class CreateComments < ActiveRecord::Migration def self.up create_table "comments", :force => true do |t| t.string :content t.integer :post_id t.integer :user_id end add_index :comments, :post_id add_index :comments, :user_id end def self.down drop_table "comments" end end
  • 77. Best Practice Lesson 5: Controller
  • 78. 1. Use before_filter Before class PostController < ApplicationController def show @post = current_user.posts.find(params[:id] end def edit @post = current_user.posts.find(params[:id] end def update @post = current_user.posts.find(params[:id] @post.update_attributes(params[:post]) end def destroy @post = current_user.posts.find(params[:id] @post.destroy end end
  • 79. 1. Use before_filter After class PostController < ApplicationController before_filter :find_post, :only => [:show, :edit, :update, :destroy] def update @post.update_attributes(params[:post]) end def destroy @post.destroy end protected def find_post @post = current_user.posts.find(params[:id]) end end
  • 80. Before 2. DRY Controller class PostController < ApplicationController def index def edit @posts = Post.all @post = Post.find(params[:id) end end def show def update @post = Post.find(params[:id) @post = Post.find(params[:id) end @post.update_attributes(params[:post]) redirect_to post_path(@post) def new end @post = Post.new end def destroy @post = Post.find(params[:id) def create @post.destroy @post.create(params[:post] redirect_to posts_path redirect_to post_path(@post) end end end
  • 81. After 2. DRY Controller http://github.com/josevalim/inherited_resources class PostController < InheritedResources::Base # magic!! nothing here! end
  • 82. After 2. DRY Controller class PostController < InheritedResources::Base # if you need customize redirect url def create create! do |success, failure| seccess.html { redirect_to post_url(@post) } failure.html { redirect_to root_url } end end end
  • 83. DRY Controller Debate!! • You lose intent and readability • Deviating from standards makes it harder to work with other programmers • Upgrading rails from http://www.binarylogic.com/2009/10/06/discontinuing-resourcelogic/
  • 84. Best Practice Lesson 6: View
  • 85. Never logic code in Views
  • 86. 1. Move code into controller Before <% @posts = Post.find(:all) %> <% @posts.each do |post| %> <%=h post.title %> <%=h post.content %> <% end %> After class PostsController < ApplicationController def index @posts = Post.find(:all) end end
  • 87. 2. Move code into model Before <% if current_user && (current_user == @post.user || @post.editors.include?(current_user) %> <%= link_to 'Edit this post', edit_post_url(@post) %> <% end %> <% if @post.editable_by?(current_user) %> After <%= link_to 'Edit this post', edit_post_url(@post) %> <% end %> class Post < ActiveRecord::Base def ediable_by?(user) user && ( user == self.user || self.editors.include?(user) end end
  • 88. Before 3. Move code into helper <%= select_tag :state, options_for_select( [[t(:draft),"draft" ], [t(:published),"published"]], params[:default_state] ) %> After <%= select_tag :state, options_for_post_state(params[:default_state]) %> # /app/helpers/posts_helper.rb def options_for_post_state(default_state) options_for_select( [[t(:draft),"draft" ],[t(:published),"published"]], default_state ) end
  • 89. 4. Replace instance variable with local variable class Post < ApplicationController def show @post = Post.find(params[:id) end end Before <%= render :partial => "sidebar" %> After <%= render :partial => "sidebar", :locals => { :post => @post } %>
  • 90. Before 5. Use Form Builder <% form_for @post do |f| %> <p> <%= f.label :title, t("post.title") %> <br> <%= f.text_field :title %> </p> <p> <%= f.label :content %> <br> <%= f.text_area :content, :size => '80x20' %> </p> <p> <%= f.submit t("submit") %> </p> <% end %>
  • 91. After 5. Use Form Builder <% my_form_for @post do |f| %> <%= f.text_field :title, :label => t("post.title") %> <%= f.text_area :content, :size => '80x20', :label => t("post.content") %> <%= f.submit t("submit") %> <% end %>
  • 92. After module ApplicationHelper def my_form_for(*args, &block) options = args.extract_options!.merge(:builder => LabeledFormBuilder) form_for(*(args + [options]), &block) end end class MyFormBuilder < ActionView::Helpers::FormBuilder %w[text_field text_area].each do |method_name| define_method(method_name) do |field_name, *args| @template.content_tag(:p, field_label(field_name, *args) + "<br />" + field_error(field_name) + super) end end def submit(*args) @template.content_tag(:p, super) end end
  • 93. 6. Organize Helper files # app/helpers/user_posts_helper.rb Before # app/helpers/author_posts_helper.rb # app/helpers/editor_posts_helper.rb # app/helpers/admin_posts_helper.rb class ApplicationController < ActionController::Base After helper :all # include all helpers, all the time end # app/helpers/posts_helper.rb
  • 94. 7. Learn Rails Helpers • Learn content_for and yield • Learn how to pass block parameter in helper • my slide about helper: http://www.slideshare.net/ihower/building-web-interface-on-rails • Read Rails helpers source code • /actionpack-x.y.z/action_view/helpers/*
  • 95. Best Practice Lesson 7: Code Refactoring
  • 96. We have Ruby edition now!! Must read it!
  • 97. Reference: : http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model http://www.matthewpaulmoore.com/ruby-on-rails-code-quality-checklist http://www.chadfowler.com/2009/4/1/20-rails-development-no-no-s : Pragmatic Patterns of Ruby on Rails Advanced Active Record Techniques Best Practice Refactoring Chad Pytel Refactoring Your Rails Application RailsConf 2008 The Worst Rails Code You've Ever Seen Obie Fernandez Mastering Rails Forms screencasts with Ryan Bates : Agile Software Development: Principles, Patterns, and Practices AWDwR 3rd The Rails Way 2nd. Advanced Rails Recipes Refactoring Ruby Edition Ruby Best Practices Enterprise Rails Rails Antipatterns Rails Rescue Handbook Code Review (PeepCode) Plugin Patterns (PeepCode)
  • 98. Thank you.