使用 Pundit 在 comment_policy_spec 上为 post 创建评论的权限

Permissions for create comments on comment_policy_spec for a post with Pundit

[更新:]

我正在使用 Pundit,当我尝试使用允许在 post 上具有角色(如经理)的用户创建评论时,我遇到了问题。

我正在练习以这种方式使用 RSpec 和多态关联进行测试,我想知道我这样做是否正确,以及如何通过此错误。 我正在使用的多态关联示例类似于 gorails 教程。

我有这个:

class CreateComments < ActiveRecord::Migration
  def change
    create_table :comments do |t|
      t.text :content
      t.references :commentable, polymorphic: true, index: true
      t.references :author, index: true

      t.timestamps null: false
    end

    add_foreign_key :comments, :users, column: :author_id
  end
end

Comment.rb

class Comment < ActiveRecord::Base
  belongs_to :author, class_name: "User"
  belongs_to :commentable, polymorphic: true

  validates :content, presence: true

  default_scope -> { order(created_at: "desc") }
  scope :persisted, lambda { where.not(id: nil) }

end

评论控制器

class CommentsController < ApplicationController
  before_action :set_post

  def create
    @comment = @commentable.comments.new(comment_params)
    @comment.author = current_user
    authorize @comment, :create?

    if @comment.save
     flash[:notice] = "Comment has been created."
     redirect_to @commentable
    else
     flash.now[:alert] = "Comment has not been created."
     render "posts/show"
    end
 end


  private

  def set_post
    @post = Post.find(params[:post_id])
  end

  def comment_params
    params.require(:comment).permit(:content)
  end
end

app/views/posts/show.html.slim

.
.
.
#comments
  = render partial: "comments/form", locals: {commentable: @post}

  - if @post.comments.persisted.any?
    h4
      = t(:available_comments, count: @post.comments.count)
    = render partial: "comments/comments", locals: {commentable: @post}
  - else
    p
      There are no comments for this post.

comments/_form.html.slim

    .header
      h3 New Comment

    = simple_form_for [commentable, Comment.new] do |f|
      .form-group
        = f.input :content, label: "Comment", placeholder: "Add a comment", input_html: { rows: 6 }
      = f.submit class: "btn btn-primary"
    <br>

路线

  resources :posts, only: [:index, :show, :edit, :update]

  resources :posts, only: [] do
    resources :comments, only: [:create], module: :posts
  end

app/controllers/post/comments_controller.rb

class Posts::CommentsController < CommentsController
  before_action :set_commentable

  private

  def set_commentable
    @commentable = Post.find(params[:post_id])
  end
end

role.rb

class Role < ActiveRecord::Base
  belongs_to :user
  belongs_to :post

  def self.available_roles
    %w(manager editor viewer)
  end
end

spec/factories/comment_factory.rb

FactoryGirl.define do
  factory :comment do
    content { "A comment!" }

    trait :post_comment do
      association :commentable, factory: :post
      #commentable_type 'Post'
      association :author_id, factory: :user
    end
  end
end

评论政策

    class CommentPolicy < ApplicationPolicy
  class Scope < Scope
   def resolve
     scope
   end
  end

 def create?
    user.try(:admin?) || record.commentable.has_manager?(user)
 end
end

spec/policies/comment_policy_spec.rb

    require 'rails_helper'    
    RSpec.describe CommentPolicy do
      context "permissions" do
        subject { CommentPolicy.new(user, comment) }

        let(:user) { create(:user) }
        let(:post) { create(:post)}
        let(:comment) { create(:comment, post: post)}

        context "for anonymous users" do
          let(:user) { nil }
          it { should_not permit_action :create }
        end

        context "for viewers of the post_comment" do
          before { assign_role!(user, :viewer, post) }
          it { should_not permit_action :create }
        end

        context "for editors of the post" do
          before { assign_role!(user, :editor, post) }
          it { should permit_action :create }
        end

        context "for managers of the post" do
          before { assign_role!(user, :manager, post) }
          it { should permit_action :create }
        end

        context "for managers of other post" do
          before do
            assign_role!(user, :manager, create(:post))
          end
          it { should_not permit_action :create }
        end

        context "for administrators" do
          let(:user) { create(:user, :admin) }
          it { should permit_action :create }
        end
      end

    end

当我 运行 我有:

`rspec spec/policies/comment_policy_spec.rb`
Run options: exclude {:slow=>true}
FFFFFF

Failure/Error: subject { CommentPolicy.new(user, comment) }

     NameError:
       undefined local variable or method `comment' for #<RSpec::ExampleGroups::CommentPolicy::Permissions::.....

我试着把评论放在评论的地方(比如可评论的,评论),但得到了同样的错误。 我试着把 post 像: subject { CommentPolicy.new(user, post) } 和工作的,但被抱怨另一个错误: rspec spec/policies/comment_policy_spec.rb

Run options: exclude {:slow=>true}
FFFFF.
Failure/Error: user.try(:admin?) || record.commentable.has_manager?(user)

     NoMethodError:
       undefined method `commentable' for #<Post:0x007f9cc3dba328>
       Did you mean?  comments

我在 subject { CommentPolicy.new(user, comment) } 上设置 CommentPolicy,在本地主机上 运行 应用程序,并尝试创建不同用户(如管理员、经理、编辑)的评论。 正如预期的那样,应用程序工作正常。

管理员和经理能够创建评论,编辑器收到消息 "You aren't allowed to do that." 并且无法按预期创建评论。所以问题出在我还不知道的 RSpec 上。

鉴于您正在使用多态性(因为您想使用 Comment 模型关联到多个模型),您将模型 Comment 定义为:

class Comment < ActiveRecord::Base
  belongs_to :commentable, polymorphic: true
  #etc...
end

belongs_to :commentable polymorphic: true允许该模型属于多个模型。它将在 table:

中创建两个字段
  1. commentable_id

  2. commentable_type

commentable_id 将是另一个 model/table 的外键,而 commentable_type 将是模型名称。这允许评论帖子或您可以添加的任何其他模型。

此定义由关联模型 Post 补充,使用:

class Post < ActiveRecord::Base
  has_many :comments, as: :commentable, dependent: :destroy
  #etc...
end

has_many :comments, as: :commentable 让 ActiveRecord 知道这是使用多态性与 Comment 关联的模型之一。

但是,在您的 Comment 模型中,您通过两种方式将评论与 Post 相关联:

belongs_to :post
belongs_to :commentable, polymorphic: true

这就是为什么您的 table 有 post_id(因为第一行)和 commentable_id(因为第二行)。创建记录时仅设置 commentable_id,因为它是通过以下方式完成的:

@post.comments.build
#this will set comment.commentable_id = @post.id, and comment.commentable_type = "Post"
#but will not set comment.post_id

综上所述,您有一个 post_id(空,导致错误)和一个 commentable_id (不为空)。 Comment 模型中的 belongs_to :post 使 ActiveRecord 使用 post_id(在 record.post.has_manager?(user) 中)搜索相关的 Post,而不是使用 commentable_id。由于 post_id 为空,因此未找到 Post。只有当用户不是管理员时才会发生错误,因为如果他是管理员,user.try(:admin?) returns true 并且不会评估句子的其余部分。将 record.post.has_manager?(user) 更改为 record.commentable.has_manager?(user)

此外,我认为您的Post政策

有误
  scope.joins(:roles).where(roles: {user_id: user}

应该是

  scope.joins(:roles).where(roles: {user_id: user.id}

我在使用可评论和作品创建评论时进行了更改!

comment_policy_spec.rb

require 'rails_helper'

RSpec.describe CommentPolicy do
  context "permissions" do
    subject { CommentPolicy.new(user, comment) }

    let(:user) { create(:user) }
    let(:post) { create(:post)}
    let(:comment) { create(:comment, commentable: post)}
    #let(:post_comment) { create(:post_comment)}

    context "for anonymous users" do
      let(:user) { nil }
      it { should_not permit_action :create }
    end

    context "for viewers of the post_comment" do
      before { assign_role!(user, :viewer, post) }
      it { should_not permit_action :create }
    end

    context "for editors of the post" do
      before { assign_role!(user, :editor, post) }
      it { should permit_action :create }
    end

    context "for managers of the post" do
      before { assign_role!(user, :manager, post) }
      it { should permit_action :create }
    end

    context "for managers of other post" do
      before do
        assign_role!(user, :manager, create(:post))
      end
      it { should_not permit_action :create }
    end

    context "for administrators" do
      let(:user) { create(:user, :admin) }
      it { should permit_action :create }
    end
  end

end

我的决赛comment_policy.rb

class CommentPolicy < ApplicationPolicy
  class Scope < Scope
   def resolve
     scope
   end
  end

 def create?
    user.try(:admin?) || record.commentable.has_manager?(user) || record.commentable.has_editor?(user) 
 end
end

再次感谢@Pablo 的帮助!