Reputation: 13243
I have this controller:
class UsersController < ApplicationController
load_and_authorize_resource
def create
@user.save
respond_with @user
end
def update
@user.update user_params
respond_with @user
end
def destroy
@user.destroy
respond_with @user
end
private
def user_params
permitted_keys = [:name,
:email,
:password,
:password_confirmation,
:lock_version]
permitted_keys << :role if can? :edit_role, @user
params.require(:user).permit(permitted_keys)
end
end
In my ability model:
can :edit_role, User do |user|
user.new_record?
end
The problem is, that when creating a user (#new
or #create
action), @user
is nil
. So the role can't be set, as it will not be added to the permitted_keys
on #create
.
How can this be solved? I could change my controller to:
permitted_keys << :role if action_name == 'create'
But I don't like this, as the Ability
can be tested much more easily than this.
Another workaround would be this:
permitted_keys << :role if can? :edit_role, @user || User.new
But this feels redundant.
Anybody has a better idea? And by the way - I'm quite surprised that passing nil
as object to can?
is allowed and doesn't raise an error. It seems that it doesn't even propagate to the actual configuration in Ability
, as otherwise user.new_record?
would raise a NoMethod
error or similar.
Upvotes: 1
Views: 664
Reputation: 13243
It seems that using CanCanCan gem, the user_params
method is called before the @user
variable is created; this kind of makes sense, as user_params
is needed to create the @user
variable, or at least it is needed to assign the values from the form.
If CanCanCan would assign a new @user
variable (of type User
, of course) before assigning the values from the form, the following code would work on #create
:
private
def user_params
permitted_keys = [:name,
:email,
:password,
:password_confirmation]
permitted_keys << :role if can? :edit_role, @user
permitted_keys << :disabled if can? :disable_user, @user
params.require(:user).permit permitted_keys
end
But as it doesn't do that apparently, the following workaround works for me:
private
def user_params
permitted_keys = [:name,
:email,
:password,
:password_confirmation]
auth_object = @user || User.new
permitted_keys << :role if can? :edit_role, auth_object
permitted_keys << :disabled if can? :disable_user, auth_object
params.require(:user).permit permitted_keys
end
Upvotes: 0
Reputation: 2378
The CanCan docs prescribe defining abilities with blocks a little differently than how you've done it. They suggest using a guard instead of a block for the case you described (when the instance is nil):
# don't do this
can :edit_role, User do |user|
user.new_record? # this won't be called for User.accessible_by(current_ability, :edit_role)
end
# do this
can :edit_role, User if user.new_record?
I'd try this. For other information and to see where I got this example, check these docs
Upvotes: 1