README.textile in troles-0.5.1 vs README.textile in troles-0.5.2
- old
+ new
@@ -11,79 +11,10 @@
The Troles project uses _role caching_ to optimize performance!
The roles list cache of a role subject (fx a user) is only updated (retrieved from data store) when the roles of the role subject changes!
Note: Troles is a full redesign of _roles generic_ and company, using lessons learned. Troles uses a much cleaner design. It is aimed at being easy to extend and easy to create adapters for etc.
-h2. Status June 7, 2011
-
-I'm now in the process of implementing TroleGroups! Finally a fully integrated role groups solution for Ruby and Rails ;)
-Due to the flexible design ot troles and trole, I can reuse the same infrastructure/design/architecture to implement trole_groups!
-*Yiii...haaa!*
-
-"A RoleGroup is simply another role_subject and can thus be configured with a given role strategy!"
-
-Simple and Sweet :)
-
-h2. The Ruby Class Decorator (RCD) pattern is born!!!
-
-I realized a while ago that I had come upon a very cool kind of "class decorator" design pattern that can be generalized to any gem that "decorates" a class (or perhaps multiple classes) with certain behavior. Gems such as _act_as_xxx_ come to mind fx.
-
-I will soon try to generalize this pattern into a separate project and then have trole, troles and trole_groups all use this to leverage their functionality. Also, I will make use of an idea that I just had while taking a cold shower (try it sometime if you have a mind block!).
-The idea is to have an internal default API, and an exposed API that by default is a reflection of the internal API. However the user is free to block parts of the API from being loaded or define his own external API that utilizes the inner API.
-
-Since this is a general purpose decorator pattern, there should also be a kind of Index where you can see which decorators have been applied to an object and what API each such decorator exposes! Cool stuff!!!!
-
-Some example usage:
-<pre>
-return if !User.has_decorator?(:troles_group)
-# do some trole group stuff!!!
-
-# later ...
-
-# list all decorators currently applied to the User class
-puts User.decorators
-
-# print the methods of the public Write API for the :troles_group decorator :)
-puts User.decorator(:troles_group).public_api(:write).methods.sort
-
-# print the methods of the internal Write API for the :troles_group decorator :)
-puts User.decorator(:troles_group).internal_api(:write).methods.sort
-</pre>
-
-I will use the new Decorator API very soon... sth like this:
-
-<pre>
-User.decorator(:trole_groups).configure(:strategy) :ref_one do |strategy|
- strategy.orm = :mongoid
- strategy.auto_load = true
-end.configure!
-</pre>
-
-Awesome!
-
-h3. Yard documentation
-
-I'm using "Yard":http://rubydoc.info/docs/yard/file/docs/GettingStarted.md for documentation.
-
-<pre>$ yard server
-
-From browser, go to: http://0.0.0.0:8808/ # then you can enjoy! the nice documentation :)
-</pre>
-
-There is now support for Caching and invalidation of the _role_list_ when the roles change.
-The specs now validate that caching works as it should.
-
-Please help out to finalize this project! :)
-
-h2. Bug hunting by running specs
-
-Run specs for at most one strategy at the time for now...
-
-@$ rspec spec/troles/strategies/bit_many_spec.rb@
-
-Please see the document _spec/Guide to running specs.textile_ where I advise on how best to do "bug hunting" to help get this project off the ground!
-
h2. Role strategies
The following lists the role strategies to be supported
* Single - one role from a set of valid roles
@@ -129,115 +60,100 @@
These strategies can be implemented for any data store using any schema format.
h3. Using roles strategies with Users and User accounts
-Roles are assigned to role subject classes such as _User_ or _UserAccount_ (YES, Devise can have multiple user accounts!). The class that has a role strategy assigned is referred to as the _role subject class_.
-Different role subject classes can have different role strategies!
+Roles are assigned to role subject classes such as _User_ or _UserAccount_ (YES, Devise can have multiple user accounts!). The class that has a role strategy assigned is referred to as the _role subject class_. Different role subject classes can have different role strategies!
When using Devise this could translate fx into a UserAccount with a "many roles" strategy and an AdminAccount with a "single role" strategy (or vice versa).
Example:
-<pre>
- require 'troles'
- require 'troles/macros'
-
- class UserAccount
- troles_strategy(:string_many).configure!
- end
+<pre>require 'troles'
+require 'troles/macros'
- class AdminAccount
- troles_strategy(:bit_one, :static => true).configure!
- end
+class UserAccount
+ troles_strategy(:string_many).configure!
+end
+
+class AdminAccount
+ troles_strategy(:bit_one, :static => true).configure!
+end
</pre>
-The special troles macros must be enabled my requiring the _trole/macros_ file.
+The special troles macros must currently be enabled my requiring the _troles/macros_ file.
+If the troles macros are not included like this, the troles DSL can be made available for an individual class by including a specific Strategy.
+Using the macros like in the above example is much easier and is recommended.
h3. Macro options
The @:static => true@ options is used to indicate, that the role can not be changed after being initially set.
-But we are getting ahead of ourselves... (more on this later).
-Troles can easily be extended to support other macro options if needed.
+But we are getting ahead of ourselves... (more on this later). Troles can easily be extended to support other macro options if needed.
+Note: This static roles functionality is currently in-progress... (used to work but under change).
h2. Roles API
The Roles API can be divided into:
* Core
-* Event/Cache
+* Event
+* Cache
* Read
* Write
-* Store
* Validation
* Operations object
-There is an equivalen Trole API for single role strategies.
+There is an equivalent Trole API for single role strategies.
h3. Event/Cache API
The User class should have an event trigger after *save* to react when the roles have changed.
If the roles were changed, an even should be sent to an _Event Manager_ to handle this. The event manager
can have subscribers to events.
Also any write event to the datastore should be predicated on _#static_roles?_ not being true for the user (thus ensuring guest roles are never updated).
-Save triggers call to Event#update_roles
-<pre>
-User
- after_save: update_roles # event handler
-</pre>
+@User.after_save: update_roles # event handler@
-<pre>
-module Troles::Api::Event
- def update_roles
- def publish_change event
-</pre>
-
h3. Roles Read API
This API operates directly on a user, fx _user#has_role?_
-<pre>
- user.has_role? :admin
- user.is_role? :editor
- user.has_any_roles? :editor, :admin
+<pre>user.has_role? :admin
+user.is_role? :editor
+user.has_any_roles? :editor, :admin
</pre>
h3. Roles Write API
This API operates directly on a user, fx _user#has_role?_
-<pre>
- user.add_role :admin
- user.remove_role :editor
+<pre>user.add_role :admin
+user.remove_role :editor
</pre>
h3. Roles Operations object
The Roles Operations object is available on user#roles
-<pre>
- user.roles + :admin
- user.roles - :editor
- user.roles << [:editor, :admin]
- user.roles.clear!
+<pre>user.roles + :admin
+user.roles - :editor
+user.roles << [:editor, :admin]
+user.roles.clear!
</pre>
h3. Creating a custom Data Store Adapter (DSA)
An adapter almost always requires a custom Config class implementation. Look at the _troles/common/config.rb_ to see what functionality is available that you can use.
Note that :single role strategies always have the namespace 'Trole' whereas for :many it is 'Troles'. This convention is used throughout troles and breaking this convention will thus break the troles functionality.
A custom Config class for _:single_ role strategies using _Mongoid_ could look sth. like this:
-<pre>
-module Trole::Mongoid
+<pre>module Trole::Mongoid
class Config < Troles::Common::Config
-
- def initialize subject_class, options = {}
+ def initialize subject, options = {}
super
end
def configure_relation
case strategy
@@ -263,12 +179,11 @@
As you can see, you have several nice convenience methods available to help set up these somewhat complex model relationships!
See the _schema_helpers.rb_ file for more details!
Example: Config class for :many roles strategies with _Mongoid_
-<pre>
-module Troles::Mongoid
+<pre>module Troles::Mongoid
class Config < Troles::Common::Config
def initialize subject_class, options = {}
super
end
@@ -303,12 +218,11 @@
Note: #set_role is only required for :single role strategies. In this case #set_roles is not required, as the superclass implementation simply calls #set_role with the first role.
Example custom SSA (encrypted role string):
-<pre>
-module Trole::Storage
+<pre>module Trole::Storage
module EncryptedStringOne < BaseOne
def initialize role_subject
super
end
@@ -342,12 +256,11 @@
h3. Custom Storage for an ORM (or data store)
In some cases it is useful to rewrite part of the base Storage functionality. One such method is #find_roles.
-<pre>
-module Troles::Storage
+<pre>module Troles::Storage
class BaseMany < Troles::Common::Storage
def find_roles *roles
role_model.where(:name => roles.flatten).all
end
...
@@ -355,12 +268,11 @@
</pre>
Active Record and Mongoid both implement the above API, so no need to customize this method in the Storage adapter.
For most other ORMs/data stores you will likely have to write your own logic to achieve this.
-<pre>
-module Troles::MongoidStorage
+<pre>module Troles::MongoidStorage
class RefMany < Troles::Storage::BaseMany
def find_roles *roles
# my own custom datastore logic!!!
end
@@ -369,14 +281,13 @@
h3. Custom data marshaller
You can also use a custom "Marshaller":http://en.wikipedia.org/wiki/Marshalling_%28computer_science%29 to store the roles in a non-standard format. This is used for the _BitMany_ strategy, which has a special _Marshaller::BitMask_ class which handles conversion between a list of symbols to an _Integer_ bitmap representing it (relative to a valid roles list!). You can create your own _Marshaller_, fx to encrypt the roles info or whatever you like!
-<pre>
-module Troles::Marshaller
+<pre>module Troles::Marshaller
class Encryption < Generic
- def initialize role_subject
+ def initialize subject
super
end
# convert marshalled value into roles symbols list
def read
@@ -393,12 +304,11 @@
h3. Using a custom Marshaller in a Storage implementation
The following example is taken from the BitOne Storage implementation that is part of troles:
-<pre>
-require 'troles/common/marshaller'
+<pre>require 'troles/common/marshaller'
module Trole::Storage
class BitOne < BaseOne
# display the role as a list of one symbol
# see Troles::Marshaller::Bitmask
@@ -430,14 +340,13 @@
Note that the same _BitMask_ marshaller is also reused in the _BitMany_ storage!
The _#bitmask_ method returns an instance of the _Bitmask_ marshaller, instantiated with the _role_subject_ (the instance that has the #role_list method, typically the user or user account). To use the _Encryption_ marshaller in an Encryption storage we could create a _#marshaller_ method:
-<pre>
- def marshaller
- @marshaller ||= Troles::Marshaller::Encryption.new role_subject
- end
+<pre>def marshaller
+ @marshaller ||= Troles::Marshaller::Encryption.new role_subject
+end
</pre>
Then use @marshaller.write(*roles)@ in the _set_xxxx_ methods and @marshaller.read@ in _#display_roles_ method of the storage, as needed.
h3. Custom troles strategy
@@ -454,12 +363,11 @@
You rarely need to implement a custom Strategy module or custom API implementations.
Here is an example of a custom Strategy implementation for _EncryptedStringMany_ that simply wraps the _BaseMany_ strategy implementation.
This example demonstrates how you can easily override functionality with custom implementations by including modules "on top".
-<pre>
-module Troles::Strategy
+<pre>module Troles::Strategy
module EncryptedStringMany
# What to add to the role subject class when this role strategy is included
# @param [Class] the role subject class to
def self.included(base)
@@ -483,55 +391,47 @@
In some cases you need a custom Base strategy that contains common functionality shared among strategies with the same singularity (one or many).
Example: _BaseMany_, used as the base for all Many roles strategies
-<pre>
- module Troles::Mongoid
- module Strategy
- module BaseMany
- # @param [Class] the role subject class for which to include the Role strategy (fx User Account)
- #
- def self.included(base)
- base.send :include, Troles::Strategy::BaseMany
+<pre>module Troles::Mongoid
+ module Strategy
+ module BaseMany
+ # @param [Class] the role subject class for which to include the Role strategy (fx User Account)
+ #
+ def self.included(base)
+ base.send :include, Troles::Strategy::BaseMany
- # base.send :include, InstanceMethods
- # base.extend ClassMethods
- end
+ # base.send :include, InstanceMethods
+ # base.extend ClassMethods
end
end
end
+end
</pre>
To use your adapter, simply pass an extra option to the _troles_strategy_ macro:
-<pre>
- User.troles_strategy(:bit_one, :orm => :mongoid).configure!
-</pre>
+@User.troles_strategy(:bit_one, :orm => :mongoid).configure!@
This even allows you to use different ORM role strategies/storages for different user accounts simultaneously!!!
Using the :auto_load option will 'auto load' (i.e require) the orm adapter from the built-in catalog of adapters that come with troles.
You can include a specific custom (or 3rd party) adapter manually. In the future it will be possible to configure troles with adapters
and specify how/where to load them from as part of this configuration!
-<pre>
- User.troles_strategy(:bit_one, :orm => :mongoid, :auto_load => true).configure!
-</pre>
+@User.troles_strategy(:bit_one, :orm => :mongoid, :auto_load => true).configure!@
You can also specify some of the options relevant to model configuration on the call to #configure if you like ;)
-<pre>
- User.troles_strategy(:bit_one, :orm => :active_record, :auto_load => true).configure! :role_model => 'Troll'
-</pre>
+@User.troles_strategy(:bit_one, :orm => :active_record, :auto_load => true).configure! :role_model => 'Troll'@
The _troles_strategy_ macro will yield the Config object if you pass it a block.
This allows you to configure your stategy with troles inside the block and then call _configure!_ on end of the block.
Using all this in combination, you could configure it all doing sth. like this:
-<pre>
-require 'my/own/active_record/adapter'
+<pre>require 'my/own/active_record/adapter'
User.troles_strategy :bit_one, :orm => :active_record do |c|
c.auto_load = false
c.valid_roles = [:troll_commander, :troll_warrior]
@@ -589,18 +489,17 @@
_troles_ will be part of a larger project under development that will go under the name "dancing tango with trolls". This will be a rework of _cream_ and _cancan-permits_ that will target use in apllications with multiple user accounts and multiple sub applications. In this new system, _dancing_ will be the replacement of _cream_ and _tango_ the replacement of _cancan-permits_. I hope to give a talk on RubyConf 2011 about this system.
h3. Guest users
-From the Devise wiki: https://github.com/plataformatec/devise/wiki/How-To:-Create-a-guest-user
+From the "Devise wiki":https://github.com/plataformatec/devise/wiki/How-To:-Create-a-guest-user
"In some applications, it's useful to have a guest User object to pass around even before the (human) user has registered or logged in. Normally, you want this guest user to persist as long as the browser session persists.
Our approach is to create a guest user object in the database and store its id in session[:guest_user_id]. When (and if) the user registers or logs in, we delete the guest user and clear the session variable. A helper function, current_or_guest_user, returns guest_user if the user is not logged in and current_user if the user is logged in."
-<pre>
-module ApplicationHelper
+<pre>module ApplicationHelper
...
# if user is logged in, return current_user, else return guest_user
def current_or_guest_user
if current_user
if session[:guest_user_id]
@@ -629,13 +528,11 @@
end
</pre>
In the new system, I propose the following:
-<pre>
-
-# this will make the current user the guest user account for the given scope!
+<pre># this will make the current user the guest user account for the given scope!
def sign_in_guest scope, options = {}
warden.set_user(guest_user.account, options.merge!(:scope => scope))
end
# sign_in :user, @user # sign_in(scope, resource)
@@ -663,12 +560,11 @@
def guest_user
session[:guest_user] ||= GuestUser.new
end
</pre>
-<pre>
-class GuestUserAccount
+<pre>class GuestUserAccount
troles_strategy(:static_one, :role => :guest) do |c|
# c.valid_roles = [:guest] not needed!
end.configure!
attr_accessor :guest
@@ -683,12 +579,11 @@
end
</pre>
Here the special _:static_many_ strategy is used, which means that whatever the _role_list_ is first set to can never change for that user.
-<pre>
-module BaseAccount
+<pre>module BaseAccount
# transfer guest settings to logged_in user/account
def transfer_guest(guest_user)
end
end
@@ -709,12 +604,11 @@
end
</pre>
And here the User setup:
-<pre>
-module BaseUser
+<pre>module BaseUser
end
class User
include Mongoid::Document
include BaseUser
@@ -724,11 +618,10 @@
has_one :blogger_account
...
end
</pre>
-<pre>
-class GuestUser
+<pre>class GuestUser
include BaseUser
def account
@guest_user_account ||= GuestUserAccount.new
end
\ No newline at end of file