Rails Best Practices
• Concept: What’s good code?
• Move Code from Controller to Model
• RESTful best practices
• Model best practices
• Controller best practices
• View best practices
1. Move Code from Controller to Model
Before
class PostsController < Application controller
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
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
2. Use model association
Before
class PostsController < ApplicationController
def create
@post = Post.new(params[:post])
@post.user_id = current_user.id
@post.save
end
end
After
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
After
class PostsController < ApplicationController
def edit
# raise RecordNotFound exception (404 error) if not found
@post = current_user.posts.find(params[:id)
end
end
4. Add model virtual attribute
Before
<% 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
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
<% form_for @user do |f| %>
<%= f.text_field :full_name %>
<% end %>
class UsersController < ApplicationController
def create
@user = User.create(params[:user)
end
end
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
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
<% 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
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
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
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
class PostController < ApplicationController
def publish @post = Post.find(params[:id])
@post.publish
redirect_to post_url(@post)
end
end