2009-05-23 44 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%肯定它会工作,但它应该很容易尝试。