Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rails 4 | Assign Role to User in Form using Bitmask | RoleModel

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?

like image 410
Georg Keferböck Avatar asked Oct 20 '25 14:10

Georg Keferböck


1 Answers

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.

like image 171
Georg Keferböck Avatar answered Oct 23 '25 04:10

Georg Keferböck



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!