简体   繁体   中英

CanCanCan: How to handle an object that is nil in Ability model?

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.

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM