I got stuck on the following issue - I suspect there is a simple solution to it, but I just can't figure it out.
I am using Role Model Gem with Rails 4.
All works well, and the assigned roles are stored perfectly well in the roles_mask attribute (integer) of the user model as internal bitmask.
Now, I would like that Admins can assign as well as remove Roles from users via a FORM (view). I am not a Rails Ninja, so there might be a trick to do this.
According to the Doc, I can do the following:
# role assignment
>> u.roles = [:admin] # ['admin'] works as well
=> [:admin]
# adding roles (remove via delete or re-assign)
>> u.roles << :manager
=> [:admin, :manager]
So that is understood.
And my approach was to query for all valid roles in the form:
# get all valid roles that have been declared
>> User.valid_roles
=> [:admin, :manager, :author]
Then list them as checkbox. Once the form gets submitted I assign / remove roles.
The question: Is that the right approach, does this even work, and if so how?
Since I had the problem not once I figured out a way - here is the solution:
More details here.
Many roles per user
I assume in this answer that you have roles in form of symbols (below I modified this solution in a way that it works with symbols) - so to work with Pundit in case you are using it.
# in models/user.rb
ROLES = [:admin, :manager, :general, :custom_role, :another_custome_role, :banned]
It is possible to assign multiple roles to a user and store it into a single integer column using a bitmask. First add a roles_mask integer column to your users table.
rails generate migration add_roles_mask_to_users roles_mask:integer rake db:migrate Next you'll need to add the following code to the User model for getting and setting the list of roles a user belongs to. This will perform the necessary bitwise operations to translate an array of roles into the integer field.
# in models/user.rb
def roles=(roles)
self.roles_mask = (roles & ROLES).map { |r| 2**ROLES.index(r) }.inject(0, :+)
end
def roles
ROLES.reject do |r|
((roles_mask.to_i || 0) & 2**ROLES.index(r)).zero?
end
end
Here a tiny modification to make it work with Rolify & Pundit
def roles=(roles)
self.roles_mask = (roles.map { |x| x.to_sym } & ROLES).map { |r| 2**ROLES.index(r) }.inject(0, :+)
end
If you're using devise without strong parameters, don't forget to add attr_accessible :roles to you user model.
If you're using devise with strong_parameters, either as a gem in a Rails 3 app, or as is built-in in Rails 4, dont forget to add the roles to the permitted list in the controller
class ApplicationController < ActionController::Base
before_filter :configure_permitted_parameters, if: :devise_controller?
protected
def configure_permitted_parameters
devise_parameter_sanitizer.for(:sign_up) {|u| u.permit(:email, :password, :password_confirmation, roles: [])}
end
end
See the section on strong parameters in the Devise documentation.
You can use checkboxes in the view for setting these roles.
<% for role in User::ROLES %>
<%= check_box_tag "user[roles][#{role}]", role, @user.roles.include?(role), {:name => "user[roles][]"}%>
<%= label_tag "user_roles_#{role}", role.humanize %><br />
<% end %>
<%= hidden_field_tag "user[roles][]", "" %>
Finally, you can then add a convenient way to check the user's roles in the Ability class.
# in models/user.rb
def is?(role)
roles.include?(role.to_s)
end
# in models/ability.rb
can :manage, :all if user.is? :admin
See Custom Actions for a way to restrict which users can assign roles to other users.
This functionality has also been extracted into a little gem called role_model (code & howto).
If you do not like this bitmask solution, see Separate Role Model for an alternative way to handle this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With