【问题标题】:Pundit policy_scope error: undefined method `admin?' for nil:NilClassPundit policy_scope 错误:未定义的方法“管理员?”对于零:NilClass
【发布时间】:2016-06-01 05:43:38
【问题描述】:

遇到了 Pundit 无法理解的问题,

使用 Rails 4.2.5.1、Pundit 1.1.0 和 Devise 进行身份验证。

我正在尝试为 BlogController#Index 操作使用策略范围。

  • 如果用户是管理员,则显示所有帖子(草稿、已发布)
  • 如果用户是标准用户,则显示标记为仅发布的帖子
  • 如果没有用户/用户未登录,则显示标记为仅发布的帖子

得到一个错误:

未定义的方法 `admin?'对于 nil:NilClass

活壳显示:

>> user
=> nil

# ApplicationController
class ApplicationController < ActionController::Base
  include Pundit
  rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized

  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  private

    def user_not_authorized
      flash[:error] = "You are not authorized to perform this action."
      redirect_to(request.referrer || root_path)
    end
end

# BlogController

# == Schema Information
#
# Table name: blogs
#
#  id         :integer          not null, primary key
#  title      :string           default(""), not null
#  body       :text             default(""), not null
#  published  :boolean          default("false"), not null
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class BlogsController < ApplicationController
  before_action :set_blog, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!, except: [:index, :show]

  after_action :verify_authorized, except: [:index, :show]
  after_action :verify_policy_scoped, only: [:index]

  def index
    @blogs = policy_scope(Blog)
    authorize @blog
  end

  def show
  end

  def new
    @blog = Blog.new
    authorize @blog
  end

  def edit
    authorize @blog
  end

  def create
    @blog = Blog.new(blog_params)
    @blog.user = current_user if user_signed_in?

    authorize @blog

    if @blog.save
      redirect_to @blog, notice: "Blog post created."
    else
      render :new
    end
  end

  def update
    authorize @blog

    if @blog.update(blog_params)
      redirect_to @blog, notice: "Blog updated."
    else
      render :edit
    end
  end

  def destroy
    authorize @blog
    @blog.destroy
    redirect_to blogs_url, notice: "Blog post deleted."
  end

  private

    def set_blog
      @blog = Blog.friendly.find(params[:id])
    end

    def blog_params
      params.require(:blog).permit(*policy(@blog|| Blog).permitted_attributes)
    end
end

# Application Policy

class ApplicationPolicy
  attr_reader :user, :record

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

  def index?
    false
  end

  def show?
    scope.where(:id => record.id).exists?
  end

  def create?
    false
  end

  def new?
    create?
  end

  def update?
    false
  end

  def edit?
    update?
  end

  def destroy?
    false
  end

  def scope
    Pundit.policy_scope!(user, record.class)
  end

  class Scope
    attr_reader :user, :scope

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

    def resolve
      scope
    end
  end
end

# Blog Policy

class BlogPolicy < ApplicationPolicy
  class  Scope < Scope
    def resolve
      if user.admin?
        scope.all
      else
        scope.where(published: true)
      end
    end
  end

  def new?
    user.admin?
  end

  def index?
    true
  end

  def update?
    user.admin?
  end

  def create?
    user.admin?
  end

  def destroy?
    user.admin?
  end

  def permitted_attributes
    if user.admin?
        [:title, :body]
    end
  end
end

在我创建的 Pundit BlogPolicy 范围内:

  class  Scope < Scope
    def resolve
      if user.admin?
        scope.order('id DESC')
      else
        scope.where('published: true')
      end
    end
  end

如果我以admin user 身份登录,它可以正常工作。

我可以查看所有博客文章。

如果我以标准user 登录,它可以工作。

标准用户会看到标记为已发布的博客文章。

如果我没有登录,user is nil 会出现错误:

NoMethodError at /blog
undefined method `admin?' for nil:NilClass

我可以在user.admin?case when 语句之前添加另一个子句elsif user.nil?,但我认为如果用户不是管理员,它应该只显示 else 块中的内容?

 # This seems wrong?

  class  Scope < Scope
    def resolve
      if user.nil?
        scope.where('published: true')
      elsif user.admin?
        scope.all
      else
        scope.where('published: true')
      end
    end
  end

任何指针都非常感谢

【问题讨论】:

    标签: ruby-on-rails ruby-on-rails-4 devise pundit


    【解决方案1】:

    发生这种情况是因为您未登录时没有用户。(可能是user变量nil的值被分配到某处,因此您试图在nil对象上调用admin?方法)

    如果您使用 ruby​​ 2.3.0 或更新版本,您最好使用safe navigation

      if user&.admin?
        scope.order('id DESC')
      else
        scope.where('published: true')
      end
    

    如果您使用其他 ruby​​ 版本

    if user.try(:admin?)
      scope.order(id: :desc)
    else
      scope.where(published: true)
    end
    

    【讨论】:

      【解决方案2】:

      你可以使用try:

      if user.try(:admin?)
        # do something
      end
      

      http://api.rubyonrails.org/v4.2.5/classes/Object.html#method-i-try

      【讨论】:

      • 谢谢 Drew,你认为如果不在 BlogController#Index 中使用 .try(:admin?) 会更好吗? policy_scope(Blog) else Blog.where(published: true)?
      • 不一定。将其移至控制器意味着如果您想在另一个位置重新使用 policy_scope,则必须手动检查用户是否再次登录。
      • 谢谢德鲁。试试吧!
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-05
      • 1970-01-01
      相关资源
      最近更新 更多