其中部分示例稍老,但其思想仍然适用
gem install rails_best_practices
rails_best_practices .
class Post
default_scope where(published: true).order("created_at desc")
end
you can't override default scope
Post.order("updated_at desc").limit(10) # =>
# Post Load (17.3ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`published` = 1 ORDER BY created_at desc, updated_at desc LIMIT 10
Post.unscoped.order("updated_at desc").limit(10) # =>
# Post Load (1.9ms) SELECT `posts`.* FROM `posts` ORDER BY updated_at desc LIMIT 10
default_scope will affect your model initialization
Post.new # =>
<Post id: nil, published: true, title: nil, created_at: nil, updated_at: nil, user_id: nil>
loop do
begin
sleep 1
eval "djsakru3924r9eiuorwju3498 += 5u84fior8u8t4ruyf8ihiure"
rescue Exception => e
puts "I refuse to fail or be stopped!"
end
end
Exception
NoMemoryError
ScriptError
LoadError
NotImplementedError
SyntaxError
SignalException
Interrupt
Timeout::Error # < ruby 1.9.2
StandardError # caught by rescue (default if no type was specified)
ArgumentError
IOError
EOFError
IndexError
LocalJumpError
NameError
NoMethodError
RangeError
FloatDomainError
RegexpError
RuntimeError
Timeout::Error # >= ruby 1.9.2
SecurityError
SocketError
SystemCallError
SystemStackError
ThreadError
TypeError
ZeroDivisionError
SystemExit
fatal
begin
# iceberg!
rescue => e
# lifeboats
end
# which is equivalent to:
begin
# iceberg!
rescue StandardError => e
# lifeboats
end
begin
# iceberg?
rescue Exception => e
# do some logging
raise e # not enough lifeboats ;)
end
# config/routes.rb
resources :auctions do
resources :bids do
resources :comments
end
end
edit_auction_bid_comment_path(@auction, @bid, @comment)
#show
, #edit
, #update
and #destroy
#index
, #new
and #create
under the resourceresources :auctions do
resources :bids, only: [:index, :new, :create]
end
resources :bids, only: [:show, :edit, :update, :destroy]
Or
resources :auctions, shallow: true do
resources :bids do
resources :comments
end
end
Or
resources :auctions do
resources :bids
end
resources :bids do
resources :comments
end
resources :comments
bid_comments GET /bids/:bid_id/comments(.:format)
POST /bids/:bid_id/comments(.:format)
new_bid_comment GET /bids/:bid_id/comments/new(.:format)
edit_comment GET /comments/:id/edit(.:format)
comment GET /comments/:id(.:format)
PATCH /comments/:id(.:format)
PUT /comments/:id(.:format)
DELETE /comments/:id(.:format)
auction_bids GET /auctions/:auction_id/bids(.:format)
POST /auctions/:auction_id/bids(.:format)
new_auction_bid GET /auctions/:auction_id/bids/new(.:format)
edit_bid GET /bids/:id/edit(.:format)
bid GET /bids/:id(.:format)
PATCH /bids/:id(.:format)
PUT /bids/:id(.:format)
DELETE /bids/:id(.:format)
auctions GET /auctions(.:format)
POST /auctions(.:format)
new_auction GET /auctions/new(.:format)
edit_auction GET /auctions/:id/edit(.:format)
auction GET /auctions/:id(.:format)
...
class PostsController < ApplicationController
def show
@item = ...
end
end
# sidebar partial use @item directly
<%= render partial: "sidebar" %>
The downside of using instance variables in partials is that you create a dependency in the partial to something outside the partial's scope (coupling). This makes the partial harder to reuse, and can force changes in several parts of the application when you want to make a change in one part.
Partials that use instance variables in partial:
###Instead, pass locals to the partials
<%= render 'reusable_partial', item: @item %>
the partial only references item and not @item.
<%= render 'reusable_partial', :item => @other_object.item %>
Also, this can be reused in contexts where there is no @item:
<%= render 'reusable_partial', :item => @duck %>
Now, because the partial only references item and not @item, the action that renders the view that renders the reusable_partial is free to change without affecting the reusable_partial and the other actions/views that render it:
If my @duck changes in the future and no longer quacks like reusable_partial expects it to (the object's interface changes), I can also use an adapter to pass in the kind of item that reusable_partial expects:
@duck changes in the future, I can also use an adapter.
<%= render 'reusable_partial', :item => itemlike_duck(@duck) %>
Check the permission by comparing the owner of object with current_user
class PostsController < ApplicationController
def edit
@post = Post.find(params[:id])
if @post.user != current_user
flash[:warning] = 'Access denied'
redirect_to posts_url
end
end
end
Just use scope access to make permission check simpler.
class PostsController < ApplicationController
def edit
# raise RecordNotFound exception (404 error) if not found
@post = current_user.posts.find(params[:id])
end
end
Code Smell
class Invoice < ActiveRecord::Base
belongs_to :user
end
<%= @invoice.user.name %>
<%= @invoice.user.address %>
<%= @invoice.user.cellphone %>
Refactor
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 %>
Bad Smell
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
class Post < ActiveRecord::Base
has_many :comments
end
class Comment < ActiveRecord::Base
belongs_to :post
scope :only_valid, -> { is_spam: false }
scope :limit, lambda { |size| { :limit => size } }
end
class CommentsController < ApplicationController
def index
@comments = @post.comments.only_valid.limit(10)
end
end
Code Smell
<% if @user.login.blank? %>
<%= link_to 'login', new_session_path %>
<% end %>
<% if @user.login.present? %>
<%= @user.login %>
<% end %>
Refactor
<% unless @user.login? %>
<%= link_to 'login', new_session_path %>
<% end %>
<% if @user.login? %>
<%= @user.login %>
<% end %>
Before
<%= render :partial => 'sidebar' %>
<%= render :partial => 'shared/sidebar', :locals => { :parent => post } %>
After
<%= render 'sidebar' %>
<%= render 'shared/sidebar', :parent => post %>
if account && account.owner && account.owner.address
...
end
Isn't identical to
if account.try(:owner).try(:address)
...
end
Isn't identical to
if account&.owner&.address
...
end
account = Account.new(owner: nil) # account without an owner
account.owner.address
# => NoMethodError: undefined method `address' for nil:NilClass
account && account.owner && account.owner.address
# => nil
account.try(:owner).try(:address)
# => nil
account&.owner&.address
# => nil
account = Account.new(owner: false)
account.owner.address
# => NoMethodError: undefined method `address' for false:FalseClass `
account && account.owner && account.owner.address
# => false
account.try(:owner).try(:address)
# => nil
account&.owner&.address
# => undefined method `address' for false:FalseClass`
try
, whatch out misspelling!account = Account.new(owner: Object.new)
account.owner.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>
account && account.owner && account.owner.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
account.try(:owner).try(:address)
# => nil
account&.owner&.address
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`
There's a stricter version of try - try!
for you:
account.try!(:owner).try!(:address)
# => NoMethodError: undefined method `address' for #<Object:0x00559996b5bde8>`