0% found this document useful (0 votes)
171 views99 pages

Rails Best Practices: As This Slide Writing, The Current Rails Version Is 2.3.4

- The document discusses various best practices for writing Ruby on Rails code, including: 1. Moving finder logic from controllers to models using named scopes. 2. Using model associations instead of direct database queries to associate data. 3. Adding virtual attributes to models to simplify controller code. 4. Using model callbacks instead of duplicating logic between the model and controller. 5. Extracting complex object creation logic into factory methods to simplify controllers.

Uploaded by

justin1983
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
171 views99 pages

Rails Best Practices: As This Slide Writing, The Current Rails Version Is 2.3.4

- The document discusses various best practices for writing Ruby on Rails code, including: 1. Moving finder logic from controllers to models using named scopes. 2. Using model associations instead of direct database queries to associate data. 3. Adding virtual attributes to models to simplify controller code. 4. Using model callbacks instead of duplicating logic between the model and controller. 5. Extracting complex object creation logic into factory methods to simplify controllers.

Uploaded by

justin1983
Copyright
© Attribution Non-Commercial (BY-NC)
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 99

Rails Best Practices

[email protected]
張文鈿
2009/10

As this slide writing, the current Rails version is 2.3.4


Who am I ?

• 張文鈿 a.k.a. ihower


• http://ihower.tw
• http://twitter.com/ihower
• http://github.com/ihower
• 來自台灣新竹 (Hsinchu, Taiwan)
Ruby Taiwan
http://ruby.tw
Agenda
• Concept: What’s good code?
• Move Code from Controller to Model
• RESTful best practices
• Model best practices
• Controller best practices
• View best practices
Warning! you should have testing before modify!
本次演講雖沒有提及測試,
但在修改重構程式前,
應有好的測試,
以確保程式於修改後執行無誤。
Best Practice Lesson 0:

Concepts
Why best practices?

• Large & complicated application


日漸複雜的程式
• Team & different coding style
團隊開發
Your code become...
• 僵硬 (Rigidity):難以修改,每改一處牽一髮動全身
• 脆弱 (Fragility):一旦修改,別的無關地方也炸到
• 固定 (Immobility):難以分解,讓程式再重用
• 黏滯 (Viscosity):彈性不夠,把事情做對比做錯還難
• 不需要的複雜度 (Needless Complexity):過度設計沒
直接好處的基礎設施
• 不需要的重複 (Needless Repetition):相同概念的程
式碼被複製貼上重複使用
• 晦澀 (Opacity):難以閱讀,無法了解意圖

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


We need good code:
我們需要好程式
What’s Good code?
• Readability 易讀,容易了解
• Flexibility 彈性,容易擴充
• Effective 效率,撰碼快速
• Maintainability 維護性,容易找到問題
• Consistency 一致性,循慣例無需死背
• Testability 可測性,元件獨立容易測試
So, What we can do?
來開始學幾招吧
Best Practice Lesson 1:

Move code from Controller to


Model
action code 超過15行請注意
http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model
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
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
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
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
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
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
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
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


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


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

class InvoiceController < ApplicationController


def create
@invoice = Invoice.new_by_user(params[:invoice], current_user)
@invoice.save
end
end
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
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
After

class PostController < ApplicationController

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

redirect_to post_url(@post)
end

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


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.


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
After

class Product < ApplicationController

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

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


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


Best Practice Lesson 2:

RESTful
請愛用 RESTful conventions
Why RESTful?
RESTful help you to organize/name controllers, routes
and actions in standardization way
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
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
Before

1. Overuse route customizations

map.resources :posts, :member => { :comments => :get,


:create_comment => :post,
:update_comment => :post,
:delete_comment => :post }
After

1. Overuse route customizations


Find another resources

map.resources :posts do |post|


post.resources :comments
end
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
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
Learn RESTful design
my slide about restful:
http://www.slideshare.net/ihower/practical-rails2-350619
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) %>


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


Before

3. Not use default route

map.resources :posts, :member => { :push => :post }

map.connect ':controller/:action/:id'
map.connect ':controller/:action/:id.:format'
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'


Best Practice Lesson 3:

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


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
After

class PostController < ApplicationController

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

end
Before

3. the Law of Demeter


class Invoice < ActiveRecord::Base
belongs_to :user
end

<%= @invoice.user.name %>


<%= @invoice.user.address %>
<%= @invoice.user.cellphone %>
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 %>
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
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
Breaking Up Models
幫 Model 減重
Before

5. Extract into Module


class User < ActiveRecord::Base

validates_presence_of :cellphone
before_save :parse_cellphone

def parse_cellphone
# do something
end

end
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
After

class User < ActiveRecord::Base

include HasCellphone

end
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
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.
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
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
Best Practice Lesson 4:

Migration
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
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
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
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
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
Best Practice Lesson 5:

Controller
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
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
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
After

2. DRY Controller
http://github.com/josevalim/inherited_resources

class PostController < InheritedResources::Base

# magic!! nothing here!

end
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
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/
Best Practice Lesson 6:

View
最重要的守則:
Never logic code in Views
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
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
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
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 } %>
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 %>


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


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
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
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/*
Best Practice Lesson 7:

Code Refactoring
We have Ruby edition now!!
Must read it!
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)
More best practices:

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

• Rails Security
http://www.slideshare.net/ihower/rails-security-3299368
感謝聆聽,請多指教。
Thank you.

You might also like