Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Defining Roles with Rolify

I am trying to make an app with Rails 4.

I am looking at role management and want to use Rolify because it supports instance level role assignment.

For others looking at the same problem, there are 2 really good answers below (I can only tick one but I used both). Check out lorefnon & Cyb3rDud3 answers below). I'm still figuring it out, but have made a migration with an array (as lorefnon shows) and the controller/routes functions (as Cyb3rDud3 shows).

What is completely baffling me is that all the documents for the Rolify gem use the console to define roles.

How do I define roles in my code?

Others on this board have asked questions which allude to them defining roles in their db:seeds file. I don't want to do that because I want to control who uses my seeds file more tightly than who can create roles.

Where do you do it?

All the examples show it being done from the console. I want to define a list of roles and then I want to give roles permissions (I want to use pundit for this part).

I have a user model. The other gem I looked at was role model. It asks you to create an array of roles in the user model. It it just so obvious that you're supposed to do that in Rolify- that none of the documents give you this step?

Where do you define the roles?

like image 887
Mel Avatar asked Nov 27 '15 04:11

Mel


3 Answers

What is completely baffling me is that all the documents for the Rolify gem use the console to define roles.

Rolify documentation does not use the console to define roles - it demonstrates how roles can be added in pure ruby. This is amazingly powerful because you can define roles whereever you can run ruby. You are not restricted to a static list of roles defined in some configuration file or in some database table.


The first question you need to ask is when do the roles get created ?

The most common use cases fall into two groups:

1. Roles are static.

Roles are created once by the application developer, support staff or company executives during application installation/deployment and during the lifetime of the running application these pre-created roles are assigned to different users.

The most common use cases of such roles includes modelling designations of a people in a company (developer, manager, support etc.), or modelling priorly known responsibilities (editor, administrator, viewer etc.)

If your roles do fall into such use cases, next thing you have to decide - whose responsibility is it to create and modify the roles. There are typically two possibilities:

1.1. Application developer himself is the person who has to add/remove/modify roles. In such cases it is best to rely on seed data or Rails migrations.

Advantage of migrations is that you can rollback the data easily if need be. This migration is additional to migration generated by rolify generators which create the schema of roles related tables for you (refer to the diagram below).

Such a migration might look like:

db/migrate/20151204083556_create_application_roles.rb

class CreateApplicationRoles < ActiveRecord::Migration
  def up
    ['admin', 'support', 'editor'].each do |role_name|
      Role.create! name: role_name
    end
  end
  def down
    Role.where(name: ['admin', 'support', 'editor']).destroy_all
  end

end

Some people rightly consider it an antipattern to have schema changes and data changes both managed by migrations. data-migrate is a gem that allows you to separate the data centric migrations from your schema migrations.

In this case and all the other cases below actual assignment of roles will happen based on user actions or application events through add_role or remove_role methods provided by rolify.This will happen during the course of the lifecycle of running application and not during application installation.

1.2 The task of adding/removing/modifying roles is done by a support team or technical executives. In such cases it would be required to provide an administrative interface for managing roles.

In this case you will have a rails controller to manage the roles. The create action will be used for creating role, show action will be there to present the role etc. These actions will have accompanying views that will provide a graphical user interface to end user to manage the roles.

2. Roles are dynamic

This category covers use cases where roles are treated more like categories or tags and can be created/modified/deleted by end users. For example a librarian can assign some role/category to a particular genre of books.

This case is similar to 1.2 because you have to handle creating/deleting/updating roles through rails controllers.


Next part is how the information is structured in your tables.

Rolify expects a specific schema (customizable to certain extent) but the expected schema is flexible enough to handle all the above use cases.

Rolify tables

like image 126
lorefnon Avatar answered Sep 18 '22 18:09

lorefnon


I've literally just gone through the same process, and like @user2860931 all I could find was some examples on how to assign roles from the console. What I required is a programmatically flexible way on how a user with the admin role or user with say the pmo role can assign those roles to others.

With a little bit of experimenting, this is how I resolved it for me. In this example I am using Devise for authentication, and Rolify for the roles.

I assume you have Devise already installed and working, therefore that you have an existing User model. Install Rolify as per the instruction on the gem page. I used the proposed name Role for the roles model. So do everything as stated here: https://github.com/RolifyCommunity/rolify. Install the GEM, generate using rolify the Role User. And migrate the database migration.

This will effectively leave you with a new table Roles and a has_and_belongs_to_many relationship with the Users table.

As for my purpose I do not require the usual Create Read (show) Update Delete (CRUD) interface for the roles, I just created a few via the seeds.rb like this.

#Seeding the Role table
#
p "Removing existing #{Role.all.count} roles"
Role.destroy_all
p "Creating 7 roles"
[:user, :admin, :portfolio_manager, :programme_manager,     :project_manager, :coordinator, :pmo].each do |role|
  Role.create( name: role )
end
p "Should have created 7 Roles, roles created: #{Role.all.count}"

I've left my additional commentary in as I do with the development stuff so I can see at a glance it worked fine. So when you run

rake db:seed

You'll have some roles setup. Alternatively you can create a roles controller and views in the usual way to allow say users with admin role add new roles.

Now the fun bit can begin. So far Devise has done everything regarding your users, or perhaps you've done your own controller anyway but you need to create your own users controller and views. As I want just a list of tick boxes for the roles against each user I've done it as follows. Here is my

users_controller.rb

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update]

  def index
    @users = User.all
  end

  def show
  end

  def edit
  end

  def update
    respond_to do |format|
      if @user.update(user_params)
        # TODO: Move hardcode flash message into language file
        format.html { redirect_to @user, notice: 'User was successfully updated.'}
        format.json { render :show, status: :ok, location: @user }
      else
        format.html { render :edit }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

  private

  def set_user
    @user = User.find(params[:id])
  end

  def user_params
    params.require(:user).permit(:username, :email, {role_ids: []})
  end
end

Now you've got to define the routes for these such that they don't clash with the Devise routes. I've kept it simple for now and resourced all the usual routes for to harden your application you may want to change that and only allow routes for those that you actually have.

routes.rb

Rails.appliction.routes.draw do
  devise_for :users
  root 'pages#home'
  resources :users    #must be after devise
end

When you now do a rake routes you'll get the usual paths for your application to make the controller work. Like this and in that order:

                  Prefix Verb   URI Pattern                      Controller#Action
        new_user_session GET    /users/sign_in(.:format)       devise/sessions#new
            user_session POST   /users/sign_in(.:format)       devise/sessions#create
    destroy_user_session DELETE /users/sign_out(.:format)      devise/sessions#destroy
           user_password POST   /users/password(.:format)      devise/passwords#create
       new_user_password GET    /users/password/new(.:format)  devise/passwords#new
      edit_user_password GET    /users/password/edit(.:format) devise/passwords#edit
                         PATCH  /users/password(.:format)      devise/passwords#update
                         PUT    /users/password(.:format)      devise/passwords#update
cancel_user_registration GET    /users/cancel(.:format)        devise/registrations#cancel
       user_registration POST   /users(.:format)               devise/registrations#create
   new_user_registration GET    /users/sign_up(.:format)       devise/registrations#new
  edit_user_registration GET    /users/edit(.:format)          devise/registrations#edit
                         PATCH  /users(.:format)               devise/registrations#update
                         PUT    /users(.:format)               devise/registrations#update
                         DELETE /users(.:format)               devise/registrations#destroy
             user_unlock POST   /users/unlock(.:format)        devise/unlocks#create
         new_user_unlock GET    /users/unlock/new(.:format)    devise/unlocks#new
                         GET    /users/unlock(.:format)        devise/unlocks#show
                    root GET    /                              pages#home
                   about GET    /about(.:format)               pages#about
                 contact GET    /contact(.:format)             pages#about
                   users GET    /users(.:format)               users#index
                         POST   /users(.:format)               users#create
                new_user GET    /users/new(.:format)           users#new
               edit_user GET    /users/:id/edit(.:format)      users#edit
                    user GET    /users/:id(.:format)           users#show
                         PATCH  /users/:id(.:format)           users#update
                         PUT    /users/:id(.:format)           users#update
                         DELETE /users/:id(.:format)           users#destroy

All that is left to do now is build the user interface, the most important part to me, and if I understood correctly to you as well. In order to build this quickly, my example is not nicely presented yet with full css magic but I'm sure you can do that to your own taste.

So to show existing users and pick them of a list, create the index.html.erb in your /app/views/users folder. Create a simple show.html.erb and an edit in which you can assign and remove the existing roles. Like this.

index.html.erb

<!-- TODO: Tidy up this file and make it look good -->
<!-- TODO: Remove hard coded text to a locale file -->
<% @users.each do |user| %>
  <p>
    <%= link_to "#{user.username}<#{user.email}>", user %>
    <%= link_to "edit", edit_user_path(user) %>
  </p>
<% end %>

show.html.erb

<!-- TODO: Tidy up this file and make it look good -->
<!-- TODO: Remove hard coded text to a locale file -->
<p>
  Username: <%= @user.username %>
</p>
<p>
  Email address: <%= @user.email %>  
</p>

<%= link_to "Back", users_path %>

edit.html.erb

<!-- TODO: Tidy up this file and make it look good -->
<!-- TODO: Remove hard coded text to a locale file -->
<p>
 Username: <%= @user.username %>
</p>
<p>
 Email address: <%= @user.email %>
</p>

<%= form_for @user do |f| %>
  <% Role.all.each do |role| %>
    <%= check_box_tag "user[role_ids][]", role.id, @user.role_ids.include?(role.id) %>
    <%= role.name %></br>
  <% end %>
  <%= f.submit %>
<% end %>

<%= link_to "Back", users_path %>

And there you have it, a simple user interface which lists all available roles from the database and provides tick boxes against a user record to enable or disable such a role. Like this:

Example of user record and role pick list

It was a little head scratcher for me as well, but hopefully this will put your on your way to increase the logic and user experience.

like image 36
Cyb3rDud3 Avatar answered Sep 19 '22 18:09

Cyb3rDud3


After reading the documentation and examples in their project pages I decided not to use a gem for managing roles in my page cause I considered it would take me lot of time to configure and to use. So instead I did the following, (I believe you used devise for your user model, even though it's not mandatory) :

If you want to have certain roles defined and "static" non-modificable but assignable from your page see below, if not jump to the next bold line

  1. Add a field called role: integer to your User model with a migration. (we use integer cause this value will be associated with one entry in the enum we define in the next step)
  2. In your file user.rb (the model), add an enum like below:

    class User < ActiveRecord::Base
      devise :registerable, #...
    
      enum role: [:admin, :normal, :premium, :moreRolesHere ]
      after_initialize :set_default_role, :if => :new_record?
    
      def set_default_role
        self.role ||= :normal
      end
    
    end
    
  3. Then in any controller or view or def you just have to get the current user or any user you want to assign a role and do as simple as the line below:

    #let's suppose we want to make premium the current user
    current_user.premium!
    
    #or someone else to be admin
    user = User.first
    user.admin!
    
  4. Then you can make your own validations in any page or controller you are working on, just by asking the role of the user:

    #see if the current user is admin
    if current_user.role == "admin"
      #do some admin stuff
    end    
    

If you want to add, modify and delete roles in your page and also assign them in there

First of all, be careful when you assign roles directly from your page. The problem here is that everyone will be able to assign himself his own role selected in a field. But if this is what you need, do as below:

  1. Create a model called Role, with a field role: string
  2. Create the controller associated to that model, roles_controller.rb
  3. Create the views you need in order to display the actions related to the management of the roles (create, edit, delete) be careful that this pages have to be only available to signed in users. The selection or addition of a role to a user you will manage inside your users controller or any other controller you want.
  4. In any other view you want to ask for the role of a user you will need to access the roles table and retrieve the one(s) corresponding to the user. The users table will need a column role_ids: text (it's text cause you need multiple roles to be saved there, all roles wrapped in an array), which will represent his roles. In your user.rb model you could have a get_roles and other defs methods to have cleaner code in controllers and views:

    class User < ActiveRecord::Base
      devise :registerable, #...
    
      serialize :role_ids
    
      #will return you an array of roles-(strings) of the user
      def get_roles
        roles = []
        role_ids.each do |role_id|
          roles << Role.find(role_id).role
        end
        return roles      
      end
    
      #ask if this user has some role-(string)
      def has_role(role)
        roles = get_roles
        return roles.include?(role)
      end
    
    end
    
  5. Lastly, of course you will need to implement the controller for roles, the create, update and destroy and all the defs associated to their views.

You could take a look at how to save arrays in database, serialize

This approach make no use of any gems related to role management or authorisation like those in the market: pundit, cancan, rolify. Leave you some links if you are skeptical with my approach and want to get your hands dirty.

like image 37
JuanM. Avatar answered Sep 18 '22 18:09

JuanM.