Rails Best Practices

99
Rails Best Practices [email protected] 張文鈿 2009/10 As this slide writing, the current Rails version is 2.3.4

description

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

Transcript of Rails Best Practices

Page 1: Rails Best Practices

Rails Best [email protected]

張文鈿2009/10

As this slide writing, the current Rails version is 2.3.4

Page 2: Rails Best Practices

Who am I ?

• 張文鈿 a.k.a. ihower

• http://ihower.tw

• http://twitter.com/ihower

• http://github.com/ihower

• 來自台灣新竹 (Hsinchu, Taiwan)

Page 3: Rails Best Practices

Ruby Taiwanhttp://ruby.tw

Page 4: Rails Best Practices

Agenda

• Concept: What’s good code?

• Move Code from Controller to Model

• RESTful best practices

• Model best practices

• Controller best practices

• View best practices

Page 5: Rails Best Practices

Warning! you should have testing before modify!

本次演講雖沒有提及測試,但在修改重構程式前,應有好的測試,

以確保程式於修改後執行無誤。

Page 6: Rails Best Practices

Best Practice Lesson 0:

Concepts

Page 7: Rails Best Practices

Why best practices?

• Large & complicated application 日漸複雜的程式

• Team & different coding style 團隊開發

Page 8: Rails Best Practices

Your code become...

• 僵硬 (Rigidity):難以修改,每改一處牽一髮動全身

• 脆弱 (Fragility):一旦修改,別的無關地方也炸到

• 固定 (Immobility):難以分解,讓程式再重用

• 黏滯 (Viscosity):彈性不夠,把事情做對比做錯還難

• 不需要的複雜度 (Needless Complexity):過度設計沒直接好處的基礎設施

• 不需要的重複 (Needless Repetition):相同概念的程式碼被複製貼上重複使用

• 晦澀 (Opacity):難以閱讀,無法了解意圖

出自 Agile Software Development: Principles, Patterns, and Practices 一書

Page 9: Rails Best Practices

We need good code:我們需要好程式

Page 10: Rails Best Practices

What’s Good code?

• Readability 易讀,容易了解

• Flexibility 彈性,容易擴充

• Effective 效率,撰碼快速

• Maintainability 維護性,容易找到問題

• Consistency 一致性,循慣例無需死背

• Testability 可測性,元件獨立容易測試

Page 11: Rails Best Practices

So, What we can do?來開始學幾招吧

Page 12: Rails Best Practices

Best Practice Lesson 1:

Move code from Controller to Model

action code 超過15行請注意http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model

Page 13: Rails Best Practices

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

Before

Page 14: Rails Best Practices

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

1.Move finder to named_scopeAfter

Page 15: Rails Best Practices

2. Use model association

class PostsController < ApplicationController

def create @post = Post.new(params[:post]) @post.user_id = current_user.id @post.save end

end

Before

Page 16: Rails Best Practices

class PostsController < ApplicationController

def create @post = current_user.posts.build(params[:post]) @post.save end

end

class User < ActiveRecord::Base has_many :postsend

2. Use model associationAfter

Page 17: Rails Best Practices

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

3. Use scope access不必要的權限檢查

Before

Page 18: Rails Best Practices

class PostsController < ApplicationController

def edit # raise RecordNotFound exception (404 error) if not found @post = current_user.posts.find(params[:id) end end

After3. Use scope access找不到自然會丟例外

Page 19: Rails Best Practices

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

Before

Page 20: Rails Best Practices

4. Add model virtual attributeclass 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

After

Page 21: Rails Best Practices

<% 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

After

Page 22: Rails Best Practices

5. Use model callback<% 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

Before

Page 23: Rails Best Practices

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

After

Page 24: Rails Best Practices

<% 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

After

Page 25: Rails Best Practices

6. Replace Complex Creation 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

Before

Page 26: Rails Best Practices

6. Replace Complex Creation 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

After

Page 27: Rails Best Practices

class InvoiceController < ApplicationController def create @invoice = Invoice.new_by_user(params[:invoice], current_user) @invoice.save endend

After

Page 28: Rails Best Practices

7. Move Model Logic into the 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

Before

Page 29: Rails Best Practices

7. Move Model Logic into the 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

After

Page 30: Rails Best Practices

class PostController < ApplicationController

def publish @post = Post.find(params[:id]) @post.publish redirect_to post_url(@post) end

end

After

Page 31: Rails Best Practices

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 :roleend

class Role < ActiveRecord::Baseend

Page 32: Rails Best Practices

<% 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

Before

Page 33: Rails Best Practices

<% 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

After

Page 34: Rails Best Practices

Before9. Nested Model Forms (one-to-one)

class Product < ActiveRecord::Base has_one :detailend

class Detail < ActiveRecord::Base belongs_to :productend

<% form_for :product do |f| %> <%= f.text_field :title %> <% fields_for :detail do |detail| %> <%= detail.text_field :manufacturer %> <% end %>

<% end %>

Page 35: Rails Best Practices

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.

Before

Page 36: Rails Best Practices

After9. Nested Model Forms (one-to-one)Rails 2.3 new feature

class Product < ActiveRecord::Base has_one :detail accepts_nested_attributes_for :detailend

<% form_for :product do |f| %> <%= f.text_field :title %> <% f.fields_for :detail do |detail| %> <%= detail.text_field :manufacturer %> <% end %> <% end

Page 37: Rails Best Practices

After

class Product < ApplicationController def create @product = Product.new(params[:product]) @product.save end end

Page 38: Rails Best Practices

10. Nested Model Forms (one-to-many)

class Project < ActiveRecord::Base has_many :tasks accepts_nested_attributes_for :tasksend

class Task < ActiveRecord::Base belongs_to :projectend

<% form_for @project do |f| %>

<%= f.text_field :name %>

<% f.fields_for :tasks do |tasks_form| %> <%= tasks_form.text_field :name %> <% end %>

<% end %>

Page 39: Rails Best Practices

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

Page 40: Rails Best Practices

Best Practice Lesson 2:

RESTful請愛用 RESTful conventions

Page 41: Rails Best Practices

Why RESTful?RESTful help you to organize/name controllers, routes

and actions in standardization way

Page 42: Rails Best Practices

class EventsController < ApplicationController

def index end def show end def create end def update end def destroy end

end

def watch_list end def add_favorite end def invite end def join end def leave end

def feeds end def add_comment end def show_comment end def destroy_comment end def edit_comment end def approve_comment end

def white_member_list end def black_member_list end def deny_user end def allow_user end def edit_managers end def set_user_as_manager end def set_user_as_member end

Before

Page 43: Rails Best Practices

After

class EventsController < ApplicationController def index; end def show; endend

class CommentsControlers < ApplicationController def index; end def create; end def destroy; end end

def FavoriteControllers < ApplicationController def create; end def destroy; endend

class EventMembershipsControlers < ApplicationController def create; end def destroy; endend

Page 44: Rails Best Practices

1. Overuse route customizations

map.resources :posts, :member => { :comments => :get, :create_comment => :post, :update_comment => :post, :delete_comment => :post }

Before

Page 45: Rails Best Practices

1. Overuse route customizationsFind another resources

map.resources :posts do |post| post.resources :comments end

After

Page 46: Rails Best Practices

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

Page 47: Rails Best Practices

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

Page 48: Rails Best Practices

Learn RESTful designmy slide about restful:

http://www.slideshare.net/ihower/practical-rails2-350619

Page 49: Rails Best Practices

2. Needless deep nesting過度設計: Never more than one level

Before

map.resources :posts do |post| post.resources :comments do |comment| comment.resources :favorites end end

<%= link_to post_comment_favorite_path(@post, @comment, @favorite) %>

Page 50: Rails Best Practices

After

map.resources :posts do |post| post.resources :commentsend map.resources :comments do |comment| comment.resources :favoritesend

<%= link_to comment_favorite_path(@comment, @favorite) %>

2. Needless deep nesting過度設計: Never more than one level

Page 51: Rails Best Practices

3. Not use default routeBefore

map.resources :posts, :member => { :push => :post } map.connect ':controller/:action/:id' map.connect ':controller/:action/:id.:format'

Page 52: Rails Best Practices

3. Not use default routeAfter

map.resources :posts, :member => { :push => :post } #map.connect ':controller/:action/:id' #map.connect ':controller/:action/:id.:format'

map.connect 'special/:action/:id', :controller => 'special'

Page 53: Rails Best Practices

Best Practice Lesson 3:

Model

Page 54: Rails Best Practices

1. Keep Finders on Their Own Modelclass 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 :postend

class CommentsController < ApplicationController def index @comments = @post.find_valid_comments endend

Before

Page 55: Rails Best Practices

1. Keep Finders on Their Own Model

class Post < ActiveRecord::Base has_many :commentsend

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) endend

After

Page 56: Rails Best Practices

2. Love named_scopeclass 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

Before

example code from Rails Antipatterns book

Page 57: Rails Best Practices

2. Love named_scopeAfter

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

Page 58: Rails Best Practices

After

class PostController < ApplicationController

def search @posts = Post.matching(:title, params[:title]) .matching(:content, params[:content]) .order(params[:order]) end

end

Page 59: Rails Best Practices

3. the Law of Demeter

class Invoice < ActiveRecord::Base belongs_to :userend

<%= @invoice.user.name %><%= @invoice.user.address %><%= @invoice.user.cellphone %>

Before

Page 60: Rails Best Practices

3. the Law of Demeter

class Invoice < ActiveRecord::Base belongs_to :user delegate :name, :address, :cellphone, :to => :user, :prefix => trueend

<%= @invoice.user_name %><%= @invoice.user_address %><%= @invoice.user_cellphone %>

After

Page 61: Rails Best Practices

4. DRY: Metaprogrammingclass 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

Before

Page 62: Rails Best Practices

4. DRY: Metaprogrammingclass 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

After

Page 63: Rails Best Practices

Breaking Up Models幫 Model 減重

Page 64: Rails Best Practices

5. Extract into Module

class User < ActiveRecord::Base validates_presence_of :cellphone before_save :parse_cellphone

def parse_cellphone # do something end end

Before

Page 65: Rails Best Practices

# /lib/has_cellphone.rbmodule 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

After

Page 66: Rails Best Practices

class User < ActiveRecord::Base

include HasCellphone

end

After

Page 67: Rails Best Practices

6. Extract to composed classBefore

# == 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

Page 68: Rails Best Practices

After6. Extract to composed class(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 endend

example code from Agile Web Development with Rails 3rd.

Page 69: Rails Best Practices

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

Before

Page 70: Rails Best Practices

class Project < ActiveRecord::Base # nothing hereend

# app/observers/project_notification_observer.rbclass ProjectNotificationObserver < ActiveRecord::Observer observe Project def after_create(project) project.members.each do |member| ProjectMailer.deliver_notice(project, member) end end

end

7. Use ObserverAfter

Page 71: Rails Best Practices

Best Practice Lesson 4:

Migration

Page 72: Rails Best Practices

1. Isolating Seed DataBefore

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" endend

Page 73: Rails Best Practices

1. Isolating Seed DataAfter

# /db/seeds.rb (Rails 2.3.4)["admin", "author", "editor","account"].each do |name| Role.create!(:name => name)end

rake db:seed

Page 74: Rails Best Practices

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

Page 75: Rails Best Practices

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" endend

Before

Page 76: Rails Best Practices

2. Always add DB indexclass 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" endend

After

Page 77: Rails Best Practices

Best Practice Lesson 5:

Controller

Page 78: Rails Best Practices

1. Use before_filterclass 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

Before

Page 79: Rails Best Practices

1. Use before_filterclass 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

After

Page 80: Rails Best Practices

2. DRY Controllerclass PostController < ApplicationController

def index @posts = Post.all end

def show @post = Post.find(params[:id) end

def new @post = Post.new end

def create @post.create(params[:post] redirect_to post_path(@post) end

end

Before

def edit @post = Post.find(params[:id) end

def update @post = Post.find(params[:id) @post.update_attributes(params[:post]) redirect_to post_path(@post) end

def destroy @post = Post.find(params[:id) @post.destroy redirect_to posts_path end

Page 81: Rails Best Practices

After

2. DRY Controllerhttp://github.com/josevalim/inherited_resources

class PostController < InheritedResources::Base # magic!! nothing here!

end

Page 82: Rails Best Practices

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

Page 83: Rails Best Practices

• You lose intent and readability

• Deviating from standards makes it harder to work with other programmers

• Upgrading rails

DRY Controller Debate!!小心走火入魔

from http://www.binarylogic.com/2009/10/06/discontinuing-resourcelogic/

Page 84: Rails Best Practices

Best Practice Lesson 6:

View

Page 85: Rails Best Practices

最重要的守則:

Never logic code in Views

Page 86: Rails Best Practices

1. Move code into controller

<% @posts = Post.find(:all) %><% @posts.each do |post| %> <%=h post.title %> <%=h post.content %><% end %>

Before

class PostsController < ApplicationController

def index @posts = Post.find(:all) end end

After

Page 87: Rails Best Practices

2. Move code into model

<% 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) %> <%= 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) endend

Before

After

Page 88: Rails Best Practices

3. Move code into helper<%= select_tag :state, options_for_select( [[t(:draft),"draft" ], [t(:published),"published"]], params[:default_state] ) %>

Before

After

<%= select_tag :state, options_for_post_state(params[:default_state]) %>

# /app/helpers/posts_helper.rbdef options_for_post_state(default_state) options_for_select( [[t(:draft),"draft" ],[t(:published),"published"]], default_state )end

Page 89: Rails Best Practices

4. Replace instance variable with local variable

<%= render :partial => "sidebar" %>

<%= render :partial => "sidebar", :locals => { :post => @post } %>

Before

After

class Post < ApplicationController def show @post = Post.find(params[:id) endend

Page 90: Rails Best Practices

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 %>

Before

Page 91: Rails Best Practices

5. Use Form BuilderAfter

<% 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 %>

Page 92: Rails Best Practices

module ApplicationHelper def my_form_for(*args, &block) options = args.extract_options!.merge(:builder => LabeledFormBuilder) form_for(*(args + [options]), &block) endend 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) endend

After

Page 93: Rails Best Practices

6. Organize Helper files

# app/helpers/user_posts_helper.rb# app/helpers/author_posts_helper.rb# app/helpers/editor_posts_helper.rb# app/helpers/admin_posts_helper.rb

class ApplicationController < ActionController::Base helper :all # include all helpers, all the timeend

# app/helpers/posts_helper.rb

Before

After

Page 94: Rails Best Practices

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/*

Page 95: Rails Best Practices

Best Practice Lesson 7:

Code Refactoring

Page 96: Rails Best Practices

We have Ruby edition now!! Must read it!

Page 97: Rails Best Practices

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)

Page 98: Rails Best Practices

More best practices:

• Rails Performancehttp://www.slideshare.net/ihower/rails-performance

• Rails Securityhttp://www.slideshare.net/ihower/rails-security-3299368

Page 99: Rails Best Practices

感謝聆聽,請多指教。Thank you.