我怎样才能重构 Pundit 政策以使其更加 DRY?

How could I could refactor a Pundit Policy to make it more DRY?

我不熟悉将 Pundit 与 Rails 一起使用,并且我的 Artist 模型有一个新策略,它按我预期的方式工作,但我不清楚重构它的好方法让它更干。具体来说,似乎我在 artists_controller.rb 中调用 authorize @artist 的次数太多了,而且我的 artist_policy.rb.

中有很多代码重复

就上下文而言,艺术家有一个名字,例如 "Claude Monet",仅此而已。

这是我的 artist_policy.rb: https://gist.github.com/leemcalilly/799d5f9136b92fcf92c6074e6a28bfdb

还有,我的 application_policy.rb: https://gist.github.com/leemcalilly/09d37a42c6f2500f98be3f1518c945e9

这是我的 artists_controller.rb: https://gist.github.com/leemcalilly/c0dd8f33416b002f3b4c9a7baf0a3a75

并且,models/artist.rb: https://gist.github.com/leemcalilly/c190322af41f3e91739b53391d8b7834

我目前使用它的方式是否正常,因为我正在清除表达每个策略(以相同的方式跨集成测试的一些重复代码是可以的),还是我应该重构它?如果是这样,人们是否有一种标准的方式来构建我所缺少的 Pundit 政策?

It seems that I'm calling authorize @artist way too many times in artists_controller.rb

老实说,我觉得你那里的东西还不错。

有几种方法可以让您可以尝试对此变得聪明,并且"automatically call authorize"每个控制器动作,但是(警告:意见基于答案) 根据过去的经验,我发现这种让它变得更干的尝试会增加很大的混乱。特别是当您最终编写了一些不需要授权或需要以不寻常方式授权的控制器操作时。

There's a lot of code duplication in my artist_policy.rb

一次一步......这是原文:

class ArtistPolicy < ApplicationPolicy
  attr_reader :user, :artist

  def initialize(user, artist)
    @user   = user
    @artist = artist
  end

  def create?
    if user.admin? || user.moderator? || user.contributor?
      true
    elsif user.banned?
      false
    end
  end

  def update?
    if user.admin? || user.moderator? || user.contributor? && user.id == @artist.user_id
      true
    elsif user.banned?
      false
    end
  end

  def destroy?
    if user.admin? || user.moderator? || user.contributor? && user.id == @artist.user_id
      true
    elsif user.banned?
      false
    end
  end
end

没有必要像这样定义自己的 initialize 方法,只要您乐于引用更通用的变量名称:record,而不是 artist(这应该定义在ApplicationPolicy):

class ArtistPolicy < ApplicationPolicy
  def create?
    if user.admin? || user.moderator? || user.contributor?
      true
    elsif user.banned?
      false
    end
  end

  def update?
    if user.admin? || user.moderator? || user.contributor? && user.id == record.user_id
      true
    elsif user.banned?
      false
    end
  end

  def destroy?
    if user.admin? || user.moderator? || user.contributor? && user.id == record.user_id
      true
    elsif user.banned?
      false
    end
  end
end

接下来,在这种情况下,可以从另一个策略规则中引用一个策略规则 - 只要它们同样适用于用户类型:

class ArtistPolicy < ApplicationPolicy
  def create?
    if user.admin? || user.moderator? || user.contributor?
      true
    elsif user.banned?
      false
    end
  end

  def update?
    if user.admin? || user.moderator? || user.contributor? && user.id == record.user_id
      true
    elsif user.banned?
      false
    end
  end

  def destroy?
    update?
  end
end

接下来,注意 record.user_id 登录用户,用于创建操作!所以你可以进一步简化这个:

class ArtistPolicy < ApplicationPolicy
  def create?
    if user.admin? || user.moderator? || user.contributor? && user.id == record.user_id
      true
    elsif user.banned?
      false
    end
  end

  def update?
    create?
  end

  def destroy?
    create?
  end
end

最后,该方法中的逻辑实际上没有错。 (您可能已经通过测试发现了这一点...)如果用户是管理员 并且 他们被禁止,那么您可能仍然希望它 return false,而不是 true。考虑到这一点,我们可以再次修复+简化代码为:

class ArtistPolicy < ApplicationPolicy
  def create?
    return false if user.banned?
    user.admin? || user.moderator? || user.contributor? && user.id == record.user_id
  end

  def update?
    create?
  end

  def destroy?
    create?
  end
end