Search code examples
ruby-on-railsrubypostgresqlruby-on-rails-4

How best to represent roles in the DB of a rails app?


I'm building authorization in my app and have three roles:

  • User
  • Moderator
  • Admin

What's the best way to represent this in Rails? I've thought about adding the following boolean fields to my user's table:

  • is_user
  • is_moderator
  • is_admin

And then creating is_user?, etc functions in the User model.

A user can have multiple roles (can be all three).


Solution

  • I suggest to use bitwise values for your access levels. This is valid because you have a limited number of roles and because your users can have several roles at once.

    It is way much faster and easier to maintain than to use an external gem.

    The idea is to use a numeric value for each access level, which can be accumulated at the bit level.

    Level     | Int val | Bit val
    user      |       1 | 00000001
    moderator |       2 | 00000010
    admin     |       4 | 00000100
    another   |       8 | 00001000
    etc ...   |      16 | 00010000
    

    So a user and a moderator would have a access level of 1 + 2 = 3 (bit 00000011). Then we can test for the bit values to define the user access_level


    1) Add a access_level column in your user model, of type integer and default 0

    2) Set constants to define your access level values

    class User < ActiveRecord::Base
      USER = 1
      MODERATOR = 2
      ADMIN = 4
    end
    

    3) Create the add_access_level and remove_access_level methods

    class User < ActiveRecord::Base
      # Add the desired bit
      def add_access_level(level)
        self.access_level |= level
      end
      # Removed the desired bit
      def remove_access_level(level)
        self.access_level &= ~level
      end
    end
    

    Note: Up to you to persist those changes in the database

    4) Create the is_user?, is_moderator? and is_admin? methods

    class User < ActiveRecord::Base
      def is_user?
        is_access_level? USER
      end
      def is_moderator?
        is_access_level? MODERATOR
      end
      def is_admin?
        is_access_level? ADMIN
      end
    
      private
        def is_access_level?(level)
          self.access_level & level == level
        end
    end
    

    Note: I would be using method_missing, but this is out of our scope here.

    5) Test it

    u = User.new
    
    u.is_admin?
    => false
    u.is_moderator?
    => false
    u.is_user?
    => false
    
    u.add_access_level User::USER
    u.add_access_level User::MODERATOR
    
    u.is_admin?
    => false
    u.is_moderator?
    => true
    u.is_user?
    => true
    
    u.remove_access_level User::MODERATOR
    
    u.is_admin?
    => false
    u.is_moderator?
    => false
    u.is_user?
    => true