如何使用Pundit解决NoMethodError

时间:2018-10-10 13:36:36

标签: ruby-on-rails ruby pundit

我不知道我在这里做错了什么,但看起来像是这样。

我使用Pundit进行授权,现在已经用它建立了一些模型。

我有一个只能由管理员创建的类别模型。另外,我也不希望用户看到显示/编辑/销毁视图。我只希望管理员可以访问它。到目前为止一切顺利。

将在下面添加一些代码:

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

我的ApplicationController中已包含Pundit,并且rescue_from Pundit::NotAuthorizedError, with: :forbidden也已在其中设置。

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

最诚挚的问候

2 个答案:

答案 0 :(得分:1)

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

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方法,该方法返回用户实例或nil。请将:home更改为您要重定向用户的位置)

答案 1 :(得分:0)

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

  1. 最明显(但有点脏)和简单明了的方法是在每种情况下检查用户的存在:

    user && user.admin?

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

  2. 您可以做的是,通过创建一个User类来使{{1}响应的GuestUser类,使Pundit认为您通过了false }}方法(https://en.wikipedia.org/wiki/Null_object_pattern):

      

    在面向对象的计算机编程中,空对象是没有引用值或具有定义的中性(“空”)行为的对象。空对象设计模式描述了此类对象的用途及其行为(或缺少行为)

    并在admin?user时使用它。实际上,它将如下所示:

    nil

    这样,您将不必更改任何其他代码,因为传递的模型将响应策略(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?,这取决于您是否希望应用程序的其他部分重用该行为。

  3. 您还可以继续使用P. Boro答案中的重定向方法。从某种意义上说,它的灵活性较差,但是如果您除了重定向所有未登​​录的用户之外不需要任何其他功能,则可以完全正常工作。