3

In my project I have a Post class, which could be a comment to an other Post with a self-join. Beside other rules, I want to enable Posts which belong to a 'published' Post as a comment

I use the following gems:

gem 'rails', '5.0.6'
gem 'cancancan', '2.1.2'

Here are the models and the ability class

class Post < ActiveRecord::Base
  belongs_to :user
  belongs_to :parent, class_name: 'Post', optional: true
  has_many :comments, class_name: 'Post', foreign_key: 'parent_id', dependent: :destroy
end

class User < ActiveRecord::Base
  has_many :posts
end

class Ability
  include CanCan::Ability

  def initialize(user)
    can :read, Post, parent: { status: %w(published), user: { id: user.id } }
  end
end

I wrote some RSpec tests, all of them should pass based on the rules above.

RSpec.describe Ability do
  subject(:ability){ Ability.new(user) }

  let!(:user) { User.create }
  let!(:commenter) { User.create }
  let!(:post) { Post.create(user: user, parent: nil, status: 'published') }
  let!(:comment) { Post.create(user: commenter, parent: post) }

  # for individual objects it works
  it { is_expected.not_to be_able_to :read, post } # pass
  it { is_expected.to be_able_to :read, comment } # pass

  # for accessible by query it fails
  it { expect(Post.accessible_by(ability)).not_to include post } # pass
  it { expect(Post.accessible_by(ability)).to include comment } # fail
end

When I run the RSpec tests, the last one fails. If you check the SQL logs, you can see that it creates the correct JOINS, but it has a wrong WHERE statement.

SELECT (...)
FROM "posts"
LEFT OUTER JOIN "posts" "parents_posts" ON "parents_posts"."id" = "posts"."parent_id"
LEFT OUTER JOIN "users" ON "users"."id" = "parents_posts"."user_id" 
WHERE "posts"."status" = 'published' 
AND "users"."id" = ?

Could you help me with it? Is it a bug in CanCanCan or I use the hash syntax incorrectly?

1 Answers1

0

Okay, I do not know if you could fix this, but I'll try to help you.

def initialize(user)
    can :read, Post, id: Post.where(parent: { status: 'published', user: { id: user.id }) }
end

try this, and tell me what return give to you.

Elis Bresciani
  • 116
  • 3
  • 13
  • Thanks, I tried the code above, but it hasn't helped me the way I hoped. At first, I had an SQL error telling me "no such column: parents.status" - I figured out we forgot to join the association Then I added `.joins(:parent)` and had to change the where part to `where(parent_posts: ...)` to use the correct name for the joined table. And that's the point where I stucked again: I got `undefined method _reflect_on_association for nil:NilClass` even if I used `joins(parent: :user)` So I see how your idea _could_ help me, but I have to solve an other issue first. I'll post my progress. – Zsolt Kozaróczy Jan 31 '18 at 22:35
  • Okay, tell me if you need any help. – Elis Bresciani Feb 01 '18 at 09:46