Module: Brassbound::Context
- Defined in:
- lib/brassbound/context.rb
Overview
This module represents the notion of the Context in the DCI paradigm.
According to en.wikipedia.org/wiki/Data,_context_and_interaction
The Context is the class (or its instance) whose code includes the roles
for a given algorithm, scenario, or use case, as well as the code to map
these roles into objects at run time and to enact the use case. Each role
is bound to exactly one object during any given use case enactment; however,
a single object may simultaneously play several roles. A context is
instantiated at the beginning of the enactment of an algorithm, scenario,
or use case. In summary, a Context comprises use cases and algorithms in
which data objects are used through specific Roles.
In Brassbound, context implementations are classes that include this module. Context implementations can use the #role method to bind roles to objects, and must provide an #execute method that enacts the use case behavior.
Brassbound enforces the fact that each role is bound to exactly one object, since each role must have a unique name. However, any given object may simultaneously play several roles.
Note that no special support is required for the role module or data object implementations; they are normal Ruby modules and objects, respectively. The context manages the necessary plumbing to bind roles to objects and to provide access to the context for the roles. In other words, role modules do not have to include any other modules or adhere to any particular conventions, and neither do the objects to which the roles are bound need to inherit from any other class or include any other modules.
Defined Under Namespace
Classes: RoleMapping
Class Method Summary collapse
-
.current ⇒ Object
The currently executing context.
Instance Method Summary collapse
-
#apply_role(role_name) ⇒ Object
Apply the role identified by role_name to the associated object.
-
#call(*args) ⇒ Object
Begin execution of this context.
-
#role(*args) ⇒ Object
Declare a role mapping.
-
#roles ⇒ Object
A Hash containing all of the declared role mappings for this context.
-
#unapply_role(role_name) ⇒ Object
Undo (most of) the effects of #apply_role.
- #undef_role(role_module, obj) ⇒ Object
Class Method Details
.current ⇒ Object
The currently executing context. Stored as a thread-local variable, so each thread can have its own current context.
35 36 37 |
# File 'lib/brassbound/context.rb', line 35 def self.current Thread.current[:brassbound_context] end |
Instance Method Details
#apply_role(role_name) ⇒ Object
Apply the role identified by role_name to the associated object. This has several effects. First, the object is extended with the role module (i.e. the “methodful role” in DCI terminology), thus adding all of the role module’s methods to the object. Second, the object has a :context method added to it, which provides the role implementation access to this context. Finally, a new method is added to this context object, which is named after the role_name and which returns the object which is mapped to that role.
99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/brassbound/context.rb', line 99 def apply_role(role_name) role_mapping = self.roles[role_name] role_module, obj = role_mapping.role_module, role_mapping.data_object obj.extend(role_module) obj.instance_variable_set(:@__brassbound_context, self) class << obj def context @__brassbound_context end end self.singleton_class.send(:define_method, role_name) do obj end end |
#call(*args) ⇒ Object
Begin execution of this context. Note that context implementations must provide an #execute method, and should not override this #call method. This method will first set Context.current to self, apply all of the role mappings that have been declared with the #role method, and then call the #execute method.
After the #execute method has returned, it will unapply all role mappings and set the current context back to its previous value.
149 150 151 152 153 |
# File 'lib/brassbound/context.rb', line 149 def call(*args) in_context do self.execute(*args) end end |
#role(*args) ⇒ Object
Declare a role mapping.
This method accepts either two or three arguments. In the three argument form, the arguments are as follows:
role_name:: The name of the role in this context.
role_module:: The module that serves as the "methodful role".
obj:: The data object to which the role_module will be attached.
In the two argument form, the role_name is omitted and the role_name is derived from the name of the role_module by converting from CamelCaseName to underscore_name. For instance, if the role_name is not specified, then a role_module called MoneySource would be named money_source.
This method adds the role mapping to the Hash returned by the #roles method. If the context is already executing when this method is invoked, it will call #apply_role to reify the role mapping immediately. Otherwise, all role mappings are applied when the context begins execution.
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 |
# File 'lib/brassbound/context.rb', line 69 def role(*args) case args.size when 2 role_module = args[0] obj = args[1] role_name = Util.underscore(role_module.name).to_sym when 3 role_name = args[0] role_module = args[1] obj = args[2] else raise ArgumentError end self.roles[role_name] = RoleMapping.new(role_module, obj) if ::Brassbound::Context.current.equal?(self) # If we are already in the execute method, apply the role mapping. apply_role(role_name) end end |
#roles ⇒ Object
A Hash containing all of the declared role mappings for this context. The keys of the Hash are the declared role names, while the values are the associated RoleMapping objects.
Role mappings are declared using the #role method, which see for further details.
45 46 47 |
# File 'lib/brassbound/context.rb', line 45 def roles @declared_roles ||= Hash.new end |
#unapply_role(role_name) ⇒ Object
Undo (most of) the effects of #apply_role. This method will remove all of the methods that were added to the mapped data object by #apply role, but the fact that the object has been extended with the role module cannot be undone. Therefore, obj.kind_of?(role_module) will still be true, even though the methods defined in role_module will have been removed from the object.
120 121 122 123 124 125 126 127 128 129 |
# File 'lib/brassbound/context.rb', line 120 def unapply_role(role_name) role_mapping = self.roles[role_name] role_module, obj = role_mapping.role_module, role_mapping.data_object obj.instance_variable_set(:@__brassbound_context, nil) role_module.instance_methods.each do |m| obj.singleton_class.send(:undef_method, m) end obj.singleton_class.send(:undef_method, :context) if obj.respond_to?(:context) self.singleton_class.send(:undef_method, role_name) end |
#undef_role(role_module, obj) ⇒ Object
131 132 133 134 135 136 137 138 139 |
# File 'lib/brassbound/context.rb', line 131 def undef_role(role_module, obj) obj.instance_variable_set(:@__brassbound_context, nil) class << obj remove_method(:context) end role_module.instance_methods.each do |m| obj.singleton_class.send(:undef_method, m) end end |