How do you deal with a :create permission in cancan that's defined by the parent object?
Asked Answered
C

2

8

Let's say you're writing the software for Blogger.

Each user can create a blog post only if they are the owner of the blog. CanCan would normally define an ability check in this circumstance as:

user.can? :create, Post

However the user can only create the post if they are the owner of the current blog and there's no way to reference the current blog using only its classname. What I really need to be able to do is:

user.can? :create, Post, @current_blog

such that in the cancan definitions I can say

can :create, Post do |post, blog|
  user == blog.owner
end

Is that possible or am I confused in how I'm approaching this?

Cockneyfy answered 21/2, 2012 at 9:46 Comment(0)
S
8

Define an ability based on the parent of the current object:

can :create, Post, :blog => { :user => user }

This will make sure the current user can only create a new Post record for a blog that they are an owner of.

  • uses Post.blog_id to find parent Blog record
  • compares Blog.user_id field to current_user.id field

Also make sure that you are using a :through declaration when loading and authorizing your Post resource, so CanCan knows who the parent record is.

PostsController:

load_and_authorize_resource :through => :blog

See the CanCan wiki on GitHub for more detail.

Sardinian answered 11/5, 2012 at 20:7 Comment(1)
The :through => :blog in the end was the deal. Thank you very much!Polled
E
0

You can use

user.can? :create_posts, @current_blog

or

user.can?:update, @current_blog

instead of

user.can? :create, Post, @current_blog

. Of course, you probably need to take out new and create action from load_and_authorize_resource. And test "authorization" by yourself

def new
unauthorized! if cannot? :create_posts, @current_blog
end

Ellaelladine answered 21/2, 2012 at 14:0 Comment(4)
do you know how you'd deal with the actual ability definition though? it's normally of the form can :create Post do |post_instance|... so it's not clear how just passing different variables would work with that?Cockneyfy
For your exemple I would use can(:create_posts, Blog) do |blog| user == blog.owner end. So test is maked on @current_blog not Post, but in the PostsController.new .Ellaelladine
I'm not sure that would work as I believe that CanCan indexes the permissions based on the type of the object passed in so if one passed in blog it would look at the blog permissions. The way that I've got around it in the end though is to setup a new object: user.can? :create, @current_blog.posts.new - that way I can pass in a post object and still read the parent off itCockneyfy
@current_blog.posts.new looks nice but it pollutes @current_blog.posts with new post instances. This can be a problem if you want to use it afterwards. Any ideas how to avoid that?Pellerin

© 2022 - 2024 — McMap. All rights reserved.