lookitsatravis/api_guardian

View on GitHub
lib/api_guardian/concerns/models/role.rb

Summary

Maintainability
B
4 hrs
Test Coverage
require 'active_support/concern'

module ApiGuardian
  module Concerns
    module Models
      module Role
        extend ActiveSupport::Concern

        included do
          self.table_name = 'api_guardian_roles'

          has_many :users, class_name: ApiGuardian.configuration.user_class.to_s
          has_many :role_permissions, class_name: ApiGuardian.configuration.role_permission_class.to_s

          validates :name, uniqueness: true, presence: true
          validates :default, uniqueness: true, if: proc { |r| r.default? }

          scope :default, -> { where(default: true) }

          # Class Methods
          def self.default_role
            default.first
          end

          def self.policy_class
            ApiGuardian::Policies::RolePolicy
          end

          # Instance Methods
          def can?(action)
            if action.is_a?(Array)
              if action.length == 1
                single_permission_check action.first
              else
                array_permission_check action
              end
            else
              single_permission_check action
            end
          end

          def cannot?(action)
            !can? action
          end

          def permissions
            arr = role_permissions_collection.map do |rp|
              rp.permission.name if rp.granted
            end.compact

            # We want to simplify returned permissions when user has "manage" for a
            # given resource by only returing that one instead of individual ones.
            arr.map do |p|
              val = nil
              if p.include? ':manage'
                val = p
              else
                resource = p.split(':')[0]
                val = p unless arr.include? "#{resource}:manage"
              end
              val
            end.compact
          end

          def create_default_permissions(granted)
            ApiGuardian.configuration.permission_class.all.each do |p|
              existing_permission = role_permissions.where(permission: p).first
              role_permissions.create(permission: p, granted: granted) unless existing_permission
            end
          end

          def add_permission(name)
            perm = ApiGuardian.configuration.permission_class.find_by_name(name)
            fail ApiGuardian::Errors::InvalidPermissionName, "Permission '#{name}' is not valid." unless perm

            role_permissions.includes(:permission).each do |rp|
              return rp.update_attribute(:granted, true) if rp.permission.name == name
            end

            role_permissions.create(permission: perm, granted: true)
          ensure
            @role_permissions_collection = nil
          end

          def remove_permission(name, destroy = false)
            role_permissions.includes(:permission).each do |rp|
              next unless rp.permission.name == name
              if destroy
                rp.destroy
              else
                rp.update_attribute(:granted, false)
              end
            end
          ensure
            @role_permissions_collection = nil
          end

          private

          def array_permission_check(actions)
            grants = []
            perms = load_permission(actions)

            unless perms.length > 0
              fail ApiGuardian::Errors::InvalidPermissionName, "Permissions '#{actions.join(', ')}' are not valid."
            end

            role_permissions_collection.each do |rp|
              grants.push rp.granted if actions.include?(rp.permission.name)
            end

            return grants.include? true if grants.count > 0 # otherwise this permission wasn't found at all

            false
          end

          def single_permission_check(action)
            perm = load_permission(action).first
            fail ApiGuardian::Errors::InvalidPermissionName, "Permission '#{action}' is not valid." unless perm

            role_permissions_collection.each do |rp|
              return rp.granted if rp.permission.name == action
            end

            false
          end

          def role_permissions_collection
            @role_permissions_collection ||= role_permissions.includes(:permission).all
          end

          def load_permission(name)
            # Basic caching mechanism to save on queries for a request
            @permissions = {} unless @permissions
            key = name.to_s

            if @permissions[key]
              return @permissions[key]
            end

            result = ApiGuardian.configuration.permission_class.where(name: name)

            @permissions[key] = result

            result
          end
        end
      end
    end
  end
end