h1. Role groups

A RoleGroup is simply another role_subject and can thus be configured with a given role strategy! Sweet :)

In some scenarios it makes sense to allow a user to be assigned one or more role groups.

<pre>

Rolegroup.get(:bloggers).set_roles :blog_admin, :admin
Rolegroup.get(:super_admin).set_roles :blog_admin, :admin
Rolegroup.create(:super_admin, :roles => [:blog_admin, :admin])

Rolegroup.create(:super_admin).set_roles :blog_admin, :admin

</pre>

<pre>

user.rolegroups << [:bloggers, :admins]
user.in_rolegroup? :bloggers

</pre>

many rolegroups from a set of valid rolegroups

Multiple roles strategy

Schema

Integer (bitmap) field on the User class
String of comma delimited role groups on User class
References to multiple RoleGroups
Embeds multiple RoleGroups (document store)

Field stored in the datastore

trolegroups

The field is named trolegroups, in order not to conflict with the method rolegroups used in the role group DSL.

These strategies can be named:

bit_many
string_many
ref_many
embed_many

These strategies can be implemented for any data store using any schema format.

<pre> User

include Troles::GroupAdapter::RefMany

</pre>

When a user is assigned a given role group, he is automatically treated as having the roles of that role group. The role group cache of the user thus changes when he is assigned or removed from a role group.

The role group however can also change, and this will effect all users assigned to that role group. The RoleGroups::EventManager must be called in all these cases.

Roles API The Roles API can be divided into

* Read operations
* Write operations and related functionality

RoleGroup Read API These methods are available on the User instance

<pre>

# any? on rolegroups_list
def in_rolegroup? rolegroup

# rolegroup_list has one element which is rolegroup
def is_rolegroup? rolegroup

# subtraction of role_groups from rolegroups_list is empty
def has_all_rolegroups? rolegroups

# union of rolegroups and rolegroups_list is not empty
def in_any_rolegroup? rolegroups

# return roles of that rolegroup
def roles_of rolegroup

# return Set of symbols,where each symbol is a rolegroup name
# This set should be cached and only invalidated when the user has a change of roles
def rolegroups_list

</pre>

RoleGroup Write API

The User class should have an event trigger after save to see if the roles were changed. If the roles were changed, an even should be sent to an event manager to handle this, invalidating role caches etc.

<pre>

User
 after_save: update_role_groups # add event handler

</pre>

These methods are available on the User instance

<pre>

# a change to the roles of the user should be published to an event handler
# this can be used to update both the Role cache of the user and fx the RolePermit cache.
# Both (and potentially others, fx for Role Groups) can subscribe to this event!
def update_role_groups
 publish_change(:role_groups) if field_changed?(rolegroups_field)
end

# check if a field on the model changed
# See http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
def field_changed? name
 send :“#{name}_changed?”
end

# can be customized
# here uses singleton EventManager
def publish_change event
 Roles::EventManager.publish_change event, :from => self
end

# return the role field used, fx :rolegroup_value etc.
# should NOT be mutable
def rolegroups_field
  :rolegroup_value
end

def add_rolegroup
role_groups << role
end

def remove_rolegroup
role_groups << role
end

# should return a RoleGroups::Operations object
def role_groups
 TRoles::RoleGroups::Operations.new(self)

class TRoles::RoleGroups:: Operations
 include ReadOperations
 include WriteOperations

 def initialize user
 end
end

</pre>

<pre>

module TRoles: RoleGroups::ReadOperations
 # check if any of the rolegroups have the given role

 def contains? role_group
   list.include? role_group
 end
 alias_method :includes?, :contains?

 # symbol list of role groups
 def list

 # Set of roles from all role groups
 def roles_list

 def get *role_groups

end

</pre>

<pre>

module TRoles: RoleGroups::WriteOperations
 def + # add role group
 alias_method <<

 def - # remove role groups
end

user.roles_groups.get(:bloggers) => returns :bloggers if in roles_list, or raises error
user.roles_groups.get(:bloggers, :admins) => returns [:bloggers, :admins], or error

if user.role_groups.roles_list == [:admin, :blogger]
if user.role_groups.have_role? :blogger

user.role_groups.add :bloggers
user.role_groups << :bloggers
user.role_groups + [:bloggers, :editors]
user.role_groups - :admins

</pre>

Relational schema

<pre> RoleGroup

has_many :roles

</pre>

<pre> Role

belongs_to :role_group
belongs_to :user

</pre>

If the RoleGroup is used in a Relational schema model, the RoleGroup should belong to a user and a Role should belong to a Group.

Roles field on RoleGroup

<pre>

RoleGroup
 def valid_roles

 belongs_to :user
 field roles (String, Integer bitmap)

</pre>

In a non-relational schema model, the RoleGroup would still belong to a User but the roles could be a field of either String or Integer (bitmap) instead of a relation to a Role model. If an integer bitmap is used, the bits would map onto the valid_roles list that returns a list of role symbols.

The valid_roles method should get the list of valid roles from a singleton such as TRoles::Configuration. The actual implementation could either use a static list, pull it from a yaml file or perhaps execute all_roles on Role.

<pre> class Role

scope :role_list, lambda { all.map {|r| name.to_sym} }

end

</pre>