2009-05-23 76 views
1

在一個無關緊要的情況下,我正在使用Ruby on Rails編寫一個博客應用程序。我的PostsController包含一些代碼,可確保登錄的用戶只能編輯或刪除他們自己的帖子。Rails:保持用戶欺騙檢查DRY

我嘗試了保理這個代碼到一個私有方法與閃光燈消息顯示一個說法,但我這樣做,並通過編輯另一位作者的帖子測試它的時候,我得到了一個ActionController::DoubleRenderError - 「只能渲染或重定向每次行動一次「。

如何保存這些檢查DRY?明顯的方法是使用before過濾器,但destroy方法需要顯示不同的閃存。

下面是相關的控制器代碼:

before_filter :find_post_by_slug!, :only => [:edit, :show] 

def edit 

    # FIXME Refactor this into a separate method 
    if @post.user != current_user 
    flash[:notice] = "You cannot edit another author’s posts." 
    redirect_to root_path and return 
    end 
    ... 
end 

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

    # FIXME Refactor this into a separate method 
    if @post.user != current_user 
    flash[:notice] = "You cannot edit another author’s posts." 
    redirect_to root_path and return 
    end 
    ... 
end 

def destroy 
    @post = Post.find_by_slug(params[:slug]) 

    # FIXME Refactor this into a separate method 
    if @post.user != current_user 
    flash[:notice] = "You cannot delete another author’s posts." 
    redirect_to root_path and return 
    end 
    ... 
end 

private 
def find_post_by_slug! 
    slug = params[:slug] 
    @post = Post.find_by_slug(slug) if slug 
    raise ActiveRecord::RecordNotFound if @post.nil? 
end 

回答

2

之前的過濾方法仍然是一個好的選擇。您可以使用控制器的action_name方法訪問請求執行的操作。

before_filter :check_authorization 

... 

protected 

def check_authorization 
    @post = Post.find_by_slug(params[:slug]) 
    if @post.user != current_user 
    flash[:notice] = (action_name == "destroy") ? 
     "You cannot delete another author’s posts." : 
     "You cannot edit another author’s posts." 
    redirect_to root_path and return false 
    end 
end 

對不起,在那裏的三元運算符。 :)當然,你可以做任何你喜歡的邏輯。

如果你願意的話,你也可以使用一個方法,如果失敗時顯式返回,避免雙重渲染。這裏的關鍵是返回,這樣你就不會渲染。

def destroy 
    @post = Post.find_by_slug(params[:slug]) 
    return unless authorized_to('delete') 
    ... 
end 

protected 

def authorized_to(mess_with) 
    if @post.user != current_user 
    flash[:notice] = "You cannot #{mess_with} another author’s posts." 
    redirect_to root_path and return false 
    end 
    return true 
end 

你可以更高(在我看來)通過拆分出行爲的不同部分簡化它(授權,處理不好授權)是這樣的:

def destroy 
    @post = Post.find_by_slug(params[:slug]) 
    punt("You cannot mess with another author's post") and return unless author_of(@post) 
    ... 
end 

protected 

def author_of(post) 
    post.user == current_user 
end 

def punt(message) 
    flash[:notice] = message 
    redirect_to root_path 
end 

就個人而言,我更喜歡卸載所有的這個例程工作到一個插件。我個人最喜歡的授權插件是Authorization。我在過去的幾年中取得了巨大的成功。

這將重構您的控制器上使用的變化:

permit "author of :post" 
+0

不要在驗證之前進行查詢! – 2009-05-23 16:18:13

1

簡單的答案是將消息更改爲適合兩個:「與其他作者的文章您不能亂」

+0

是的,但我真的不想那樣做。 – 2009-05-23 16:07:59

1

如果你不喜歡醜*回報在這最後的解決方案,你可以使用一個圍繞過濾器和有條件地產生只有當用戶授權。

around_filter :check_authorization, :only => [:destroy, :update] 

private 
def check_authorization 
    @post = Post.find_by_slug(params[:slug]) 
    if @post.user == current_user 
     yield 
    else 
     flash[:notice] = case action_name 
     when "destroy" 
      "You cannot delete another author's posts." 
     when "update" 
      "You cannot edit another author's posts." 
     end 
     redirect_to root_path 
    end 
end 

* - 這是我的偏好,儘管代碼方面它是完全有效的。我只是覺得這種風格明智,它往往不適合。

我也應該添加我沒有測試過這個,我不是100%肯定它會工作,但它應該很容易嘗試。