0

I have a dropdown for changing the role of a user in my Rails 4.2 app with CanCanCan. The User::ROLES array of strings holds the different possible roles that a user can have. To display the roles in a dropdown:

<%= f.select :role, options_for_select((User::ROLES), @user.role) %>

I want to limit the dropdown options to roles that the currently signed-in user can :manage according to CanCanCan. Roles that the current user does not have :manage permissions on should not be populated in the dropdown. Any guidance is appreciated.

edit:

Sean Huber's answer is perfect if comparing against the string literals. However, I realized the issue is that I define this array in my User model, but I need to be comparing against the roles that are set one level deeper in ability.rb. Here's an example of how permissions on the User model are set in ability.rb:

This makes sense, however I realized it won't really work since it's comparing to the literals in the User::ROLES array.

The code in ability.rb that sets roles access looks like this. I need to check against the specific role specified:

if user.role? :superadmin
  can :manage, User, role: 'admin'
end

How can I compare against the specific roles that the :superadmin tokenized role has :manage access over instead of the literals in User::ROLES?

The definition of ROLES from User.rb:

ROLES = %w[student teacher school district reseller admin superadmin god]

  • how do you know which roles they can manage? – Tall Paul Aug 10 '16 at 13:26
  • 1
    @TallPaul roles that users can manage are kept in the `app/models/ability.rb` class. I've updated the question with an example. –  Aug 10 '16 at 13:58
  • Given your edit, you might want to look at the answer to this question: http://stackoverflow.com/questions/9809203/get-a-string-that-represents-a-users-cancan-abilities – Sean Huber Aug 10 '16 at 14:13

3 Answers3

0

Whatever action is being performed in your controller should have some code that sets the @user field and a assembles a collection of roles usable by your view. In your case, it's a matter of deciding in the controller method what roles should be included in the collection being passed to the view.

The simplest approach iterates through your roles, tests them using CanCanCan and then adds them to a filtered collection that your options_for_select can use.

def some_method

  @user = User.find(params[:id])
  @roles = Array.new

  User::ROLES each do | role |
    if can? :manage role
      @roles.push(role)
    end
  end

end
MarsAtomic
  • 10,436
  • 5
  • 35
  • 56
0

Here is a one line solution:

<%= f.select :role, options_for_select(User::ROLES.map{|r| can?(:manage, r) ? r : nil}.compact, @user.role) %>
Sean Huber
  • 3,945
  • 2
  • 26
  • 31
  • This is perfect but I've updated the original question with an issue with this solution. –  Aug 10 '16 at 14:04
0

I ended up using take to solve this:

<%= f.select :role, options_for_select(User::ROLES.take(User::ROLES.index(current_user.role)), @user.role) %>

  • what does take do? – Tall Paul Aug 10 '16 at 15:13
  • 1
    grabs the passed number of elements from an array. Here I use it to grab it by using the `current_user.role` as the index. It works because my roles are in ascending order in the `User::ROLES` array. http://ruby-doc.org/core-2.3.1/Enumerable.html#method-i-take –  Aug 10 '16 at 17:30