如何使用 Pundit 解决 NoMethodError

How to solve NoMethodError with Pundit

我不知道我是不是哪里做错了,但好像是这样。

我使用 Pundit 进行授权,我现在已经用它设置了几个模型。

我有一个只能由管理员创建的类别模型。此外,我也不希望用户看到 show/edit/destroy 视图。我只是想让管理员访问它。到目前为止一切顺利。

将在下面添加一些代码:

category_policy.rb

class CategoryPolicy < ApplicationPolicy
  def index?
    user.admin?
  end

  def create?
    user.admin?
  end

  def show?
    user.admin?
  end

  def new?
    user.admin?
  end

  def update?
    return true if user.admin?
  end

  def destroy?
    return true if user.admin?
  end
end

categories_controller.rb

class CategoriesController < ApplicationController
  layout 'scaffold'

  before_action :set_category, only: %i[show edit update destroy]

  # GET /categories
  def index
    @category = Category.all
    authorize @category
  end

  # GET /categories/1
  def show
    @category = Category.find(params[:id])

    authorize @category
  end

  # GET /categories/new
  def new
    @category = Category.new
    authorize @category
  end

  # GET /categories/1/edit
  def edit
    authorize @category
  end

  # POST /categories
  def create
    @category = Category.new(category_params)
    authorize @category
    if @category.save
      redirect_to @category, notice: 'Category was successfully created.'
    else
      render :new
    end
  end

  # PATCH/PUT /categories/1
  def update
    authorize @category
    if @category.update(category_params)
      redirect_to @category, notice: 'Category was successfully updated.'
    else
      render :edit
    end
  end

  # DELETE /categories/1
  def destroy
    authorize @category
    @category.destroy
    redirect_to categories_url, notice: 'Category was successfully destroyed.'
  end

  private

  # Use callbacks to share common setup or constraints between actions.
  def set_category
    @category = Category.find(params[:id])
  end

  # Only allow a trusted parameter "white list" through.
  def category_params
    params.require(:category).permit(:name)
  end
end

application_policy.rb

class ApplicationPolicy
  attr_reader :user, :record

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

  def index?
    false
  end

  def create?
    create?
  end

  def new?
    create?
  end

  def update?
    false
  end

  def edit?
    update?
  end

  def destroy?
    false
  end

  class Scope
    attr_reader :user, :scope

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

    def resolve
      scope.all
    end
  end
end

我已将 Pundit 包含在我的 ApplicationController 中,rescue_from Pundit::NotAuthorizedError, with: :forbidden 也在那里设置。

授权本身有效,如果我使用管理员帐户登录,我可以访问/categories/*。如果我注销,我会得到以下信息:NoMethodError at /categories undefined methodadmin?'对于 nil:NilClass` 在写问题时我想我发现了问题 - 我猜 Pundit 寻找一个 nil 的用户,因为我没有登录。解决这个问题的正确方法是什么?

此致

最常见的方法是将用户从未登录用户无法访问的页面重定向。只需在您的控制器中添加一个之前的操作:

class CategoriesController < ApplicationController
  before_action :redirect_if_not_logged_in

  <...>

  private

  def redirect_if_not_logged_in
    redirect_to :home unless current_user
  end
end

(我在这里假设您有 current_user 方法,其中 returns 用户实例或 nil。请将 :home 更改为您想要重定向用户的任何位置)

有多种方法可以实现您想要的。

  1. 最明显(但有点脏)和最直接的方法是在每种情况下添加对用户存在的检查:

    user && user.admin?

    它不会因 nil 错误而失败,因为条件的第二部分不会被执行。但它看起来不太好,对吧?特别是如果您必须将其复制到 CategoryPolicy.

  2. 中的所有方法
  3. 您可以做的是通过创建一个 GuestUser class 来使 Pundit "think" 成为您通过的 Userfalseadmin? 方法 (https://en.wikipedia.org/wiki/Null_object_pattern):

    In object-oriented computer programming, a null object is an object with no referenced value or with defined neutral ("null") behavior. The null object design pattern describes the uses of such objects and their behavior (or lack thereof)

    并在usernil时使用。实际上,它看起来像这样:

    class ApplicationPolicy
      attr_reader :user, :record
    
      def initialize(user, record)
        @user = user || GuestUser.new
        @record = record
      end
    
      # ...
    end
    
    class GuestUser
      def admin?
        false
      end
    end
    

    这样您就不必更改任何其他代码,因为您传递的模型会响应策略 (admin?) 预期的方法。您可能希望在其他地方(而不是在策略文件中)定义此 GuestUser,具体取决于您是否希望应用程序的其他部分重用该行为。

  4. 您也可以继续使用 P. Boro 回答中的重定向方法。它在某种意义上不太灵活,但如果除了重定向所有未登​​录的用户之外不需要任何东西,它完全可以正常工作。