Hydra::Grouper
Details
A work in progress. See the example implementation for details. The big benefit of this refactor is we can say:
For a given user, what are all of my application abilities; That is to say what can I specifically do. It still requires reading the ruby code for the specific details of each ApplicationAbility that I have.
Glossary
- User - A single person
- Group - Users may be members of groups
- FunctionalRole - For combining the application abilities that are all needed to perform a conceptual activity (think of this as a molecule). There is a relationship between a FunctionalRole and either a User or a Group. We are working on naming that concept (for now lets call it assignee).
- Hyrax::ApplicationAbility - see below (think of this as an atom)
Example Implementation
class User < ActiveRecord::Base
has_many :groups, through: :group_memberships
has_many :group_memberships
has_many :functional_roles, as: :assignee
end
class GroupMembership < ActiveRecord::Base
belongs_to :user
belongs_to :group
end
class Group < ActiveRecord::Base
has_many :group_memberships
has_many :users, through: :group_memberships
has_many :functional_roles, as: :assignee
end
class FunctionalRole < ActiveRecord::Base
belongs_to :assignee, polymorphic: true
has_many :functional_abilities
end
class FunctionalAbility < ActiveRecord::Base
belongs_to :functional_role
def application_ability_class
# We have a column :application_ability_class_name
# with an example value of Hyrax::ApplicationAbility::AdminApplicationAbility
application_ability_class_name.constantize
end
end
module Hyrax # This should perhaps be pushed into the Hydra namespace.
module ApplicationAbility
# @api public
#
# Applies the functional abilities to the given ability based on the given ability's current_user.
# This allows for us to plugin additional abilities in implementing applications without the explicit need
# to re-open the Ability class. (more on that later).
#
# @param [Ability] ability - an object that implements the CanCan::Ability interface
# @return [Ability]
def self.append_abilities_to!(ability:)
functionality_abilities_for(user: ability.current_user).each do |functional_ability|
functional_ability.new(ability: ability).apply
end
ability
end
# @api public
#
# For the given user, return an enumerable of all of the ApplicationAbility objects
# that apply, based on the user and their group memberships relation to their Functional Role
# at the institution
#
# @param [User] user for which we are looking up their assigned functional abilities.
# @return [Array<Hyrax::ApplicationAbility::BaseApplicationAbility]
# @note This is the place for the new and improved user groups to be leveraged
def self.functionality_abilities_for(user:)
# TODO: Define an implementation of how this is looked up
end
# An abstract class that defines the interface for implementations of a ApplicationAbility.
# A ApplicationAbility is a name-able atomic unit of permissions.
class BaseApplicationAbility
extend Forwardable
def initialize(ability:)
@ability = ability
end
def_delegators :@ability, :can, all # etc.
def apply
raise NotImplementedError, "Subclasses must implement #apply"
end
end
# The set of specific cans and cannots that are applicable for an Admin.
class AdminApplicationAbility < BaseApplicationAbility
def apply
# NOTE: We are removing the short-circuit tradition of a guard `return unless admin?`
# This ApplicationAbility will not be applied if it is not one of your functional roles at the institution.
can :read, :admin_dashboard
end
end
end
module Ability
extend ActiveSupport::Concern
# @note assumes we are previously including Hydra::Ability
def hydra_default_permissions
super
apply_functional_abilities
end
private
def apply_functional_abilities
Hyrax::ApplicationAbility.append_abilities_to!(ability: self)
end
end
end