Rails:保持用户欺骗检查DRY

时间:2009-05-23 15:45:35

标签: ruby-on-rails ruby refactoring

在非原创性方面,我正在使用Ruby on Rails编写博客应用程序。我的PostsController包含一些代码,可确保登录用户只能编辑或删除自己的帖子。

我尝试将此代码分解为一个私有方法,其中只有一个参数可以显示flash消息,但是当我这样做并通过编辑其他作者的帖子进行测试时,我得到了ActionController::DoubleRenderError - “只能每个动作渲染或重定向一次“。

如何保留这些检查DRY?显而易见的方法是使用前置过滤器,但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

3 个答案:

答案 0 :(得分: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"

答案 1 :(得分:1)

简单的答案是将消息更改为适合两者的内容:“你不能弄乱其他作者的帖子。”

答案 2 :(得分:1)

如果您不喜欢最后一个解决方案中的丑陋*返回,您可以使用around过滤器并仅在用户获得授权时有条件地生成。

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%肯定它会起作用,尽管它应该很容易尝试。