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>