nil:NilClass 的 Pundit 策略错误未定义方法“图像”

Pundit policy error undefined method `image' for nil:NilClass

我已经被这个问题困扰了很长一段时间,不确定我做错了什么。

我正在使用 Rails 4.2.5.1、Pundit 1.1.0 和 Devise。

我有一个博客 post,其中显示以下内容:

索引页面显示正确(作者用户名除外,它不显示,因为它不识别用户名参数)。但是,当我尝试通过显示页面查看个人 post 时,出现以下错误:

undefined method `image' for nil:NilClass

如果我删除了显示图像的那行代码,我会收到标题错误,其中包含相同的未定义方法错误。

我几乎完全按照 SitePoint-source/Authorization_with_Pundit 中的示例进行了策略和控制器(仅进行了少量修改)

在添加 Pundit 以在管理员、编辑和用户之间创建授权之前,一切都运行良好。

这是我当前的代码:

应用程序控制器

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
  before_filter :configure_permitted_parameters, if: :devise_controller?

  private

    def user_not_authorized
      flash[:alert] = "Access denied. You are not authorized to view that page."
      redirect_to (request.referrer || root_path)
    end


protected

  def configure_permitted_parameters
    devise_parameter_sanitizer.permit(:sign_up) { |u| u.permit(:username, :email, :password, :password_confirmation, :remember_me) }
    devise_parameter_sanitizer.permit(:sign_in) { |u| u.permit(:username, :email, :password, :remember_me) }
    devise_parameter_sanitizer.permit(:account_update) {|u| u.permit(:username, :email, :password, :password_confirmation, :current_password)}
  end


end

Post 控制器

class PostsController < ApplicationController
  before_action :set_post, only: [:show, :edit, :update, :destroy]
  after_action :verify_authorized, only: [:destroy]
  after_action :verify_policy_scoped, only: [:user_posts]

  def index
    @meta_title = "Blog"
    @meta_description = "description here"
    @posts = Post.all.order("created_at DESC").paginate(:page => params[:page], :per_page => 4)
  end

  def show
  end

  def new
    @meta_title = "Add New Blog"
    @meta_description ="Add a new blog to your profile."
    @post = Post.new
  end

  def edit
    @meta_title = "Edit Blog"
    @meta_description ="Edit an existing blog from your profile."
  end

  def create
    @post = Post.new
    @post.update_attributes(permitted_attributes(@post))

    if @post.save
      redirect_to @post, notice: 'Post was successfully created.'
    else
      render :new
    end
  end

  def update

    @post = Post.find(params[:id])
    if @post.update_attributes(permitted_attributes(@post))
      redirect_to @post, notice: 'Post was successfully updated.'
    else
      render :edit
    end
  end

  def destroy
    if @post.present?
      authorize @post
      @post.destroy
    else
      skip_authorization
    end

    redirect_to posts_url, notice: 'Post was successfully deleted.'
  end

  def user_posts
    @posts = policy_scope(Post)
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_post
      @post = Post.find_by(id: params[:id])
    end

    # Only allow the white list through.
    def post_params
      params.require(:post).permit(policy(@post).permitted_attributes)
    end
end

申请政策

class ApplicationPolicy
  attr_reader :user, :record

  def initialize(user, record)
    raise Pundit::NotAuthorizedError, "You must be logged in to perform this action" unless user
    @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

Post 政策

class PostPolicy < ApplicationPolicy
  class Scope < Scope
    def resolve
      scope.where(user: user)
    end
  end

  def permitted_attributes
    if user.admin? || user.editor?
      [:title, :body, :image, :permalink, :description, :tag_list, :username]
    else
      [:title, :body, :image, :username]
    end
  end

  def new?
    user.admin? || user.editor?
  end

  def index?
    true
  end

  def create?
    user.admin? || user.editor?
  end

  def update?
    user.admin? || user.editor? || record.user == user
  end

  def destroy?
    user.admin? || record.user == user
  end
end

Post.rb

class Post < ActiveRecord::Base
    include ActiveModel::ForbiddenAttributesProtection
    belongs_to :user

    # This method associates the attribute ":image" with a file attachment
    has_attached_file :image, styles: { 
        thumb: '100x100>',
        square: '200x200#',
        medium: '300x300>',
    }

    extend FriendlyId
    friendly_id :permalink, use: [:slugged, :history, :finders]
    validates :permalink, presence: true, uniqueness: true
    validates :title, presence: true, length: { minimum: 5}
    validates :description, presence: true, uniqueness: true, length: {maximum: 160}
    validates :body, presence: true
    validates :image, presence: true
    # Validate the attached image is image/jpg, image/png, etc
    validates_attachment_content_type :image, :content_type => /\Aimage\/.*\Z/

    def should_generate_new_friendly_id?
        permalink_changed?
    end
end

Post#show

<% provide(:title, "@post.title") %>
<% provide(:description, "@post.description") %>

<div class="row">
  <div class="col-md-offset-1 col-md-10">
    <div class="panel panel-default">
      <div class="panel-heading center">
        <%= image_tag @post.image.url, :style => "width: 100%; height: auto;" %>
      </div>
      <div class="panel-body">
        <h2 class="title center"><%= @post.title %></h2>
        <p class="posted"><i class="ion-android-time"></i> <%= @post.created_at.strftime("%B %d, %Y") %> </p>
        <p class="posted"><i class="ion-person"></i> Author: <%= link_to @post.username, about_path(:anchor => "coaches") %></p>
        <hr>
        <div class="postBody" id="summernote">
          <%= @post.body.html_safe %>
        </div>
      </div>
      <div class="panel-footer center">
        <%= link_to 'Back', posts_path %> |
        <%= link_to 'Edit', edit_post_path(@post) %> | 
        <%= link_to 'Delete', @post, method: :delete, data: { confirm: 'Are you sure you want to delete this post?' } %>
        <%= render 'disqus' %>
      </div>
      <div class="panel-footer center">
        <%= link_to 'Back', posts_path %>
      </div>
    </div>
  </div>
</div>

Post#index

<div class="container">
  <div class="row">
    <div class="col-md-9">
      <% @posts.each do |post| %>
        <div class="post-wrapper">
          <h3 class="title center"><%= link_to post.title, post %></h3>
          <p class="posted"><i class="ion-android-time"></i> <%= post.created_at.strftime("%B %d, %Y") %></p>
          <p class="posted"><i class="ion-person"></i> Author: <%= link_to post.user(:username), about_path(:anchor => "coaches") %></p><br>
          <div class="post-image center"><%= link_to image_tag(post.image.url, :style => "width: 100%; height: auto;"), post %></div><br>

            <%= sanitize(post.body[0,300]) %>...<br>
            <div class="center">
              <%= link_to 'View Blog', post, class: "btn btn-primary" %>
              <% if policy(post).update? %>
                <%= link_to 'Edit', edit_post_path(post) %> |
              <% end %>
              <% if policy(post).destroy? %>
                <%= link_to 'Delete', post, method: :delete, data: { confirm: 'Are you sure?' } %>
              <% end %>
            </div>
          <br>
        </div>
      <% end %>
      <div class="center">
        <%= will_paginate @posts, renderer: BootstrapPagination::Rails %>
      </div>
    </div>
  </div>
</div>

我还有一些其他问题,希望在解决此问题后自行解决:

这些其他问题可以稍后解决,或者如果您看到错误,我将不胜感激。

现在我的主要问题是尝试解决 undefined method "image" for nil:NilClass 错误

undefined method `image' for nil:NilClass

这意味着您尝试调用 .image 的对象(即 @post)是 nil。追溯,找出为什么它是零。

在您的 Posts#show 视图中,您依靠 set_post 回调来设置您的 post。 set_post 回调使用 Post.find_byid 参数来查找记录。

find_by 的行为方式是 return nil 如果给定的参数是 nil(即如果你调用 Post.find_by(id: nil),你会找回 nil)。这可能表明 params[:id] 本身就是 nil - 检查它是否设置为查询字符串参数(示例。com/posts/show?<i><b> id=12</b></i>) 或作为 URL 本身的一部分 (example.com/posts/<i><b>12</b></i>).

如果您不知道,请在您的 Posts#show 操作中添加一个 byebug 调用:

def show
  byebug
end

这将在执行时停止操作,并为您提供一个控制台 - 此时,您可以键入 params[:id] 以查看其值。

我建议您不要使用 Post.find_by,而是使用 Post.find。不同之处在于 find 默认使用 ID(因此您无需指定您使用的是哪个参数),并且它引发 404 Not Found 响应而不是 returning nil 如果找不到记录。为此,您的 set_post 回调应如下所示:

def set_post
  @post = Post.find params[:id]
end