Search code examples
ruby-on-railsdeviseruby-on-rails-5pundit

How to customise devise to store user role information in session?


Currently we are using two separate table for Users and Roles.

I am using pundit for authorisation and devise for authentication.

At many places I am doing current_user.roles to fetch the roles of the user. Mostly inside pundit policy files.

I want to store the User roles in the session when user logs in. So that I will not query db each time to fetch the roles.

Any quick solution will be greatly appreciated ?


Solution

  • Since Pundit have no options, to pass session or other parameter, except current_user and checking entity, you can use Rails.cache instead:

    # app/models/user.rb
    class User < ActiveRecord::Base
      # ...
      def cached_roles
        Rails.cache.fetch "user.#{self.id}.roles" do
          roles.pluck(:name)
        end
      end
    
      def clear_cached_roles
        Rails.cache.delete "user.#{self.id}.roles"
      end
    end
    
    
    # app/policies/post_policy.rb
    class PostPolicy < ApplicationPolicy
      # ...
    
      def admin?
        user.cached_roles.include? 'admin'
      end
    
      def reader?
        user.cached_roles.include? 'reader'
      end
    end
    

    To make Devise to cache current roles, you need to override Devise's session_controller

    # app/controllers/users/sessions_controller.rb
    class Users::SessionsController < Devise::SessionsController
      def create
        super
        current_user.cached_roles
      end
    
      def destroy
        current_user.clear_cached_roles
        super
      end
    end
    

    I created this demo rails application, you can play with it: see my solution variant with Rails.cache in rails_cache_solution branch or in pull request.

    Also see these files for more details:

    • app/controllers/users/sessions_controller.rb
    • spec/controllers/posts_controller_spec.rb
    • app/policies/post_policy.rb
    • app/models/user.rb
    • README.md