【问题标题】:How could I could refactor a Pundit Policy to make it more DRY?我怎样才能重构 Pundit 政策以使其更加干燥?
【发布时间】:2018-05-17 14:47:58
【问题描述】:

我是使用 Pundit 和 Rails 的新手,并且我的 Artist 模型有一个新策略,它可以按我的预期工作,但我不清楚重构它以使其更干燥的好方法。具体来说,我在artists_controller.rb 中调用authorize @artist 的次数似乎太多了,而且我的artist_policy.rb 中有很多代码重复。

就上下文而言,艺术家有一个名字,例如“克劳德·莫奈”,仅此而已。

这是我的 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 政策?

【问题讨论】:

    标签: ruby-on-rails ruby dry pundit


    【解决方案1】:

    看来我在artists_controller.rb 中调用authorize @artist 的次数太多了

    老实说,我认为你所拥有的一切都很好。

    可以通过几种方法尝试对此进行巧妙处理,并为每个控制器操作“自动调用authorize”,但是(警告:基于意见的答案) 从过去的经验中,我发现这种使它更干燥的尝试会增加严重的混乱。尤其是当您最终编写了一些不需要授权或需要以不寻常方式授权的控制器操作时。

    我的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
    

    最后,该方法中的逻辑实际上几乎没有错误。 (您可以通过测试来了解它...)如果用户是管理员并且他们被禁止,那么您可能仍然希望它返回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
    

    【讨论】:

    • 可以进一步简化此操作,但除非您发现此逻辑在应用程序中其他地方重复很多次,否则我不会打扰。例如,您可以定义一个方法:User#admin_or_moderator?,也可以在ApplicationPolicy 中定义一个方法,例如record_belongs_to_contributor?.
    • 非常感谢汤姆!这是一个非常有用的答案。非常感谢您花时间逐步引导我完成它。从中学到了很多。
    • 您现在也可以使用alias_methodgithub.com/varvet/pundit#just-plain-old-ruby
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-17
    • 1970-01-01
    相关资源
    最近更新 更多