[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.
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:
commentable_id
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}