How do I use cancan to authorize an array of resources?
I have a non-restful controller that I am trying to use the cancan authorize! method to apply permissions to.
I have a delete_multiple action that starts like so
def delete_multiple
@invoices = apparent_user.invoices.find(params[:invoice_ids])
I want to check that the user has permission to delete all of these invoices before proceeding. If I use
authorize! :delete_multiple, @invoices
permission is refused. My ability.rb includes the following
if user.admin?
can :manage, :all
elsif user.approved_user?
can [:read, :update, :destroy, :delete_multiple], Invoice, :user_id => user.id
end
Is it a matter of looping through my array and calling authorize individually or is there a smarter way of doing things? I'm starting to feel like doing authorizations would be easier manually than by using cancan for a complicated non-restful controller (although I have plenty of ot开发者_StackOverflow社区her restful controllers in my app where it works great).
A little late in here but you can write this in your ability class
can :delete_multiple, Array do |arr|
arr.inject(true){|r, el| r && can?(:delete, el)}
end
EDIT
This can be written also as:
can :delete_multiple, Array do |arr|
arr.all? { |el| can?(:delete, el) }
end
It seems that authorize!
only works on a single instance, not an array. Here's how I got around that with Rails 3.2.3 and CanCan 1.6.7.
The basic idea is to count the total records that the user is trying to delete, count the records that are accessible_by (current_ability, :destroy)
, then compare the counts.
If you just wanted an array of records that the user is authorized to destroy, you could use the array returned by accessible_by (current_ability, :destroy)
. However I'm using destroy_all
, which works directly on the model, so I wound up with this count-and-compare solution.
It's worthwhile to check the development log to see how the two SELECT COUNT
statements look: the second one should add WHERE
phrases for the authorization restrictions imposed by CanCan.
My example deals with deleting multiple messages.
ability.rb
if user.role_atleast? :standard_user
# Delete messages that user owns
can [:destroy, :multidestroy], Message, :owner_id => user.id
end
messages_controller.rb
# Suppress load_and_authorize_resource for actions that need special handling:
load_and_authorize_resource :except => :multidestroy
# Bypass CanCan's ApplicationController#check_authorization requirement:
skip_authorization_check :only => :multidestroy
...
def multidestroy
# Destroy multiple records (selected via check boxes) with one action.
@messages = Message.scoped_by_id(params[:message_ids]) # if check box checked
to_destroy_count = @messages.size
@messages = @messages.accessible_by(current_ability, :destroy) # can? destroy
authorized_count = @messages.size
if to_destroy_count != authorized_count
raise CanCan::AccessDenied.new # rescue should redirect and display message
else # user is authorized to destroy all selected records
if to_destroy_count > 0
Message.destroy_all :id => params[:message_ids]
flash[:success] = "Permanently deleted messages"
end
redirect_to :back
end
end
精彩评论