Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

Polymorphic Confusion #269

Closed
codezomb opened this Issue Feb 9, 2011 · 6 comments

Comments

Projects
None yet
2 participants

codezomb commented Feb 9, 2011

This is probably out of the scope of what cancan is designed to do, but I thought I'd ask since I've been stuck for a while. I've put my models, and ability file into a gist for easy reading.

https://gist.github.com/34551acfdff01e19df9f

The situation is this... Groups have many posts, although each post is either for member only viewing, or public viewing. This is indicated via a string column named 'viewable_by'. Now, if you are a member of the group or higher, you should be able to view any of the groups posts. However, if you are not a member you can only view the posts marked as public.

When I run an index test in irb, I ask for all posts via accessible_by(current_ability). This returns all posts, ignoring permissions set in the ability file. However, if I loop through each one, I get the proper permissions displayed per record. The result of this is shows in my gist.

Normally I would consider forgoing the polymorphic, but we have plans to use this relationship with other models in the future. I would love to be able to use cancan for this. I just can't seem to wrap my head around it.

Thanks in advance.

Owner

ryanb commented Feb 9, 2011

Thanks for the detailed gist, that really helps. The problem is you are passing both a hash of conditions and a block in the Post permission.

    can :read, Post, :viewable_by => 'members' do |post|
      post.postable.members.include? user
    end

This won't work as you expect here so I should change it so it raises an exception. I'll mark this ticket as such, so don't close it when you've resolved this problem.

You should either use a hash of conditions or a block, not both. So here you can switch it to a block.

    can :read, Post do |post|
      post.viewable_by == 'members' && post.postable.members.include?(user)
    end

But then the problem is you cannot use the accessible_by call because it will not be able to interpret the block into SQL (it will raise an exception). Instead you can provide your own custom SQL which defines the permissions.

    can :read, Post, ["posts.viewable_by = 'members' and posts.postable_id in (select groups.id from groups left join memberships ... where memberships.user_id = ?)", user.id] do |post|
      post.viewable_by == 'members' && post.postable.members.include?(user)
    end

I wasn't able to include the entire SQL in there, but hopefully it will give you an idea.

Thanks! That does provide the desired result. However, I'm curious as to how this would work with multiple "postable" models... For example, adding an "organization" model, and making it postable... This, from what I can see would either require an addition to the current SQL, in the form of another join, or maybe ( if possible ) another ability definition, separate from the first. This is something we plan to do either during initial development, or after beta. So I'm trying to think about it proactively.

We have quite a few relationships defined this way, that are going to have similar permissions. Looks like I'm going to have my work cut out for me.

Thanks again for all the hard work, and contributions! Railscasts has by far been the most helpful rails resource I have found!

Owner

ryanb commented Feb 10, 2011

Do they all have the same memberships join and you're checking user_id on that? If so you can make it all one SQL query like this:

def initialize(user)
  can :read, Post, ["posts.viewable_by = 'members' and (#{sql_for_post_member})", {:user_id => user.id}] do |post|
    post.viewable_by == 'members' && post.postable.members.include?(user)
  end
end

def sql_for_post_member
  %w[groups organizations].map do |postable|
    "posts.postable_id in (select groups.id from groups left join memberships ... where memberships.user_id = :user_id)"
  end.join(" or ")
end

Then you just have to add any polymorphic association table names to the array.

Once again, you've made my day. That's a really nice solution, thanks.

Owner

ryanb commented Feb 10, 2011

Cool, glad you got it working.

Owner

ryanb commented Mar 9, 2011

raise an error when trying to make a rule with both hash conditions and a block - closed by 0de43c4

This issue was closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment