0

[UPDATED:]

I'm using Pundit and I'm facing problems when I try to use for a user who is permitted with a role(like manager) on posts to create a comment.

I'm practicing to do in this way testing with RSpec and polymorphic association and I want to know if I doing correct, and how to do for pass this error. The sample for do a polymorphic association I'm using like the gorails tutorial.

I have this:

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

CommentsController

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>

routes

  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

CommentPolicy

    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

When I run I have:

`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::.....

I tryed to put in the place of comment( like commentable, comments) and gets the same error. And I tryed to put post like: subject { CommentPolicy.new(user, post) } and work's, but was complained another errors: 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

I put on CommentPolicy the subject { CommentPolicy.new(user, comment) } and I run the application on localhost and try to create a comment with different users like(admin, manager, editor). As expected the application work's fine.

The admin and manager was able to create a comment, and the editor receive the message "You aren't allowed to do that." and wasn't able to create the comment as expected. So the problem is something on the RSpec that I don't know yet.

rld
  • 2,603
  • 2
  • 25
  • 39

2 Answers2

1

Giving that you are using polymorphism (because you want to use the Comment model to be associated to more than one model), you defined the model Comment as:

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

The belongs_to :commentable polymorphic: true allows this model to belong to more than one model. It will create two fields in the table:

  1. commentable_id

  2. commentable_type

commentable_id will be a foreign key to another model/table and commentable_type will be the model name. This allows to comment posts, or any other model you may add.

This definition is complemented by the associated model, Post, using:

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

has_many :comments, as: :commentable lets ActiveRecord know this is one of the models associated to Comment using polymorphism.

However, in your Comment model you associated the comment to a Post in two ways:

belongs_to :post
belongs_to :commentable, polymorphic: true

This is why your table has post_id (because of the first line) and commentable_id (because of the second line). Only commentable_id is set when the record is created, as it is done through:

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

For all said, you have a post_id (null, causing the error) and a commentable_id (not null). belongs_to :post in Comment model makes ActiveRecord search the related Post using post_id (in record.post.has_manager?(user)), instead of using commentable_id. As post_id is null, the Post is not found. The error only happens if the user is not admin, because if he is an admin, user.try(:admin?) returns true and the rest of the sentence is not evaluated. Change record.post.has_manager?(user) for record.commentable.has_manager?(user)

Besides, I think you have an error in PostPolicy

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

Should be

  scope.joins(:roles).where(roles: {user_id: user.id}
Pablo
  • 3,004
  • 1
  • 12
  • 19
  • Hello @Pablo it wasn't this. Thank's for reply! – rld Mar 07 '18 at 21:25
  • Hello @Pablo, I remove the belongs_to :post in Comment model and the post_id still remains null the value. I'm trying in this way for practice and for future models that will use this type of polymorphic association. Thank's for help! – rld Mar 07 '18 at 23:12
  • But I was thinking too this Post firstly will have like two types of comments: one for comment_reviews(who will use the managers and editors or admins) and then when the post will be published the users. – rld Mar 07 '18 at 23:14
  • post_is remains null because it is not used. When you create a comment it is linked to the post using commentable_id. This is the only field needed. If the comment belongs to a post, commentable_id will represent a post_id (and commentable_type will be "Post"). If you comment other model (using polymorphism), commentable_type will be the other model name and commentable_id will represent the id of another table. – Pablo Mar 08 '18 at 00:22
  • Yes, I understand. I remove the post_id there and I decide to rename the column text for content. I do this now like the tutorial of the [gorails](https://gorails.com/episodes/comments-with-polymorphic-associations), I like this sample code there and I'm using it. – rld Mar 10 '18 at 14:24
  • But, I stopped on the restriction CommentPolicy with pundit. When I try to do a record.post.has_manager?(user) throws that error. I'm thinking on switch for do with has_many this next week. Thank you @Pablo! – rld Mar 10 '18 at 14:25
  • Change `record.post.has_manager?(user)` for `record.commentable.has_manager?(user)`. Given that it's polymorphic, you don't know in advance if commentable is a post or other model (you only have one model in your example. – Pablo Mar 10 '18 at 17:08
  • Hello @Pablo I update my question and showing what I changed and what I'm facing know, I do the comment factory in a different way and know my specs are working for creating the comments, but for now I'm facing problems with the CommenPolicy. – rld Mar 12 '18 at 12:24
  • Thank's for your help! – rld Mar 12 '18 at 12:24
0

I changed when creates a comment using commentable and work's!

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

my final 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

Thank you again for your help @Pablo!

rld
  • 2,603
  • 2
  • 25
  • 39