Professional Documents
Culture Documents
Rails Best Practices: As This Slide Writing, The Current Rails Version Is 2.3.4
Rails Best Practices: As This Slide Writing, The Current Rails Version Is 2.3.4
ihower@gmail.com
2009/10
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
Concepts
(Rigidity)
(Needless Repetition)
(Opacity)
(Fragility)
(Immobility)
(Viscosity)
(Needless Complexity)
Readability
Flexibility
Effective
Maintainability
Consistency
Testability
Before
end
end
After
Before
After
Before
After
Before
After
After
Before
After
After
Before
After
After
Before
After
After
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
After
Before
Before
After
After
http://railscasts.com/episodes/75-complex-forms-part-3
RESTful
RESTful conventions
Why RESTful?
RESTful help you to organize/name controllers, routes
and actions in standardization way
Before
class EventsController < ApplicationController
def index
end
def feeds
end
def white_member_list
end
def watch_list
end
def show
end
def add_comment
end
def black_member_list
end
def add_favorite
end
def create
end
def show_comment
end
def deny_user
end
def invite
end
def update
end
def destroy_comment
end
def allow_user
end
def join
end
def destroy
end
def edit_comment
end
def edit_managers
end
def leave
end
def approve_comment
end
def set_user_as_manager
end
def set_user_as_member
end
end
After
Before
After
Before
After
Before
After
Model
Before
After
2. Love named_scope
Before
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
After
Before
After
4. DRY: Metaprogramming
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
Before
4. DRY: Metaprogramming
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
After
Breaking Up Models
Model
Before
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
Before
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.
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
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
After
Migration
Before
After
After
Before
After
Controller
1. Use before_filter
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
Before
1. Use before_filter
After
2. DRY Controller
Before
def edit
@post = Post.find(params[:id)
end
def show
@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 new
@post = Post.new
end
def create
@post.create(params[:post]
redirect_to post_path(@post)
end
end
def destroy
@post = Post.find(params[:id)
@post.destroy
redirect_to posts_path
end
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
After
Upgrading rails
from http://www.binarylogic.com/2009/10/06/discontinuing-resourcelogic/
View
After
After
Before
After
Before
After
<%= render :partial => "sidebar", :locals => { :post => @post } %>
Before
After
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
After
#
#
#
#
app/helpers/user_posts_helper.rb
app/helpers/author_posts_helper.rb
app/helpers/editor_posts_helper.rb
app/helpers/admin_posts_helper.rb
Code Refactoring
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)
Rails Performance
Rails Security
http://www.slideshare.net/ihower/rails-performance
http://www.slideshare.net/ihower/rails-security-3299368
Thank you.