drsi 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.ruby-gemset +1 -0
- data/.ruby-version +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +100 -0
- data/Rakefile +2 -0
- data/drsi.gemspec +25 -0
- data/examples/money_transfer.rb +80 -0
- data/lib/drsi/dci/context.rb +170 -0
- data/lib/drsi/dci/role.rb +18 -0
- data/lib/drsi/module.rb +28 -0
- data/lib/drsi/object.rb +6 -0
- data/lib/drsi/rolable.rb +66 -0
- data/lib/drsi/version.rb +3 -0
- data/lib/drsi.rb +6 -0
- data/spec/context_spec.rb +117 -0
- data/spec/interaction_spec.rb +48 -0
- data/spec/players_spec.rb +83 -0
- data/spec/role_spec.rb +33 -0
- data/spec/roleplayers_spec.rb +107 -0
- data/spec/spec_helper.rb +12 -0
- metadata +89 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2e2a89f6f00a413fd3ae85e494e75e0f4bcc4aac
|
4
|
+
data.tar.gz: b44a051e6678c13077ae65a55e8c11ba36530128
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: abd2d518e9b8952cfff3d150e323e0c440529aadf51c5dc2d2df388c8e93353d86bdd542faeaaeb276635ca503ce1cfa48b7bc8a43f1a8c94fa3facf18a7fb40
|
7
|
+
data.tar.gz: eae8418a3b0e7949b569cc73a510998d6a7a358ce4836d1548eb5c713b31ee0b79fca8c69cf51b7641c93bfa72c41e547b9edf2bc5393108e7faf01b487ec7aa
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.ruby-gemset
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
drsi
|
data/.ruby-version
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby-2.1.0
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2014 Lorenzo Tello
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# drsi
|
2
|
+
|
3
|
+
**_Trygve Reenskaug_**, the parent of MVC, proposes an evolution to traditional OO paradigm: [The DCI Architecture: A New Vision of Object-Oriented Programming](http://www.artima.com/articles/dci_vision.html "The DCI Architecture: A New Vision of Object-Oriented Programming").
|
4
|
+
|
5
|
+
This gem makes Data-Context-Interaction paradigm ready to be used in your Ruby application. See also [Data Context Interaction: The Evolution of the Object Oriented Paradigm](http://rubysource.com/dci-the-evolution-of-the-object-oriented-paradigm/ "Data Context Interaction: The Evolution of the Object Oriented Paradigm").
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Install as usual, either with rubygems
|
10
|
+
|
11
|
+
gem install drsi
|
12
|
+
|
13
|
+
or including it in your Gemfile and running bundle install:
|
14
|
+
|
15
|
+
# Gemfile
|
16
|
+
gem "drsi"
|
17
|
+
|
18
|
+
$ bundle install
|
19
|
+
|
20
|
+
_**Note**: only **ruby 2.1+** compatible._
|
21
|
+
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
dci-ruby gives you the class DCI::Context to inherit from to create your own contexts:
|
26
|
+
|
27
|
+
class MoneyTransfer < DCI::Context
|
28
|
+
|
29
|
+
# Roles
|
30
|
+
|
31
|
+
role :source_account do
|
32
|
+
def transfer(amount)
|
33
|
+
self.balance -= amount
|
34
|
+
target_account.get_transfer(amount)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
role :target_account do
|
39
|
+
def get_transfer(amount)
|
40
|
+
self.balance += amount
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
# Interactions
|
46
|
+
|
47
|
+
def run(amount = settings(:amount))
|
48
|
+
source_account.transfer(amount)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
Every context defines some roles to be played by external objects (players) and their interactions. This way
|
53
|
+
you have all the agents and operations in a use case wrapped in just one entity instead of spread out throughout the
|
54
|
+
application code.
|
55
|
+
|
56
|
+
Use the defined contexts, instantiating them, wherever you need in your code:
|
57
|
+
|
58
|
+
MoneyTransfer.new(:source_account => Account.new(1),
|
59
|
+
:target_account => Account.new(2)).run(100)
|
60
|
+
|
61
|
+
or the short preferred way:
|
62
|
+
|
63
|
+
MoneyTransfer[:source_account => Account.new(1),
|
64
|
+
:target_account => Account.new(2),
|
65
|
+
:amount => 100]
|
66
|
+
|
67
|
+
Inside a context instance, every player object incorporates the behaviour (methods) defined by its role while keeping its own.
|
68
|
+
|
69
|
+
The Account instances above are players. They are accesible inside #run through #source_account and #target_account private methods.
|
70
|
+
Also, every role player has private access to the rest of role players in the context.
|
71
|
+
|
72
|
+
Unlike the Presenter approach in dci-ruby (where the object to play a role and the one inside the context playing it are associated but different), this extending/unextending approach preserves unique identity of objects playing roles.
|
73
|
+
|
74
|
+
When instanciating a Context, the extra no-role pairs given as arguments are read-only attributes accessible via #settings:
|
75
|
+
|
76
|
+
MoneyTransfer[:source_account => Account.new(1),
|
77
|
+
:target_account => Account.new(2),
|
78
|
+
:amount => 500]
|
79
|
+
|
80
|
+
here, :amount is not a player (has no associated role) but is still privately accessible both in the interactions and the roles
|
81
|
+
via #settings(:amount).
|
82
|
+
|
83
|
+
|
84
|
+
See the [examples](https://github.com/ltello/drsi/tree/master/examples) folder for examples of use and the [drsi-DCI-Sample](https://github.com/ltello/drsi-DCI-Sample) repository for a sample Rails application using DCI through this gem.
|
85
|
+
|
86
|
+
Notice how your models and controllers are not overloaded anymore. They are thinner and simpler.
|
87
|
+
Also note how now most of the functionality of the system is isolated, totally dry-ied and easily maintainable in the different context classes.
|
88
|
+
|
89
|
+
## Contributing
|
90
|
+
|
91
|
+
1. Fork it
|
92
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
93
|
+
3. Commit your changes (`git commit -am 'Added some feature'`)
|
94
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
95
|
+
5. Create new Pull Request
|
96
|
+
|
97
|
+
|
98
|
+
## Copyright
|
99
|
+
|
100
|
+
Copyright (c) 2012, 2013 Lorenzo Tello. See LICENSE.txt for further details.
|
data/Rakefile
ADDED
data/drsi.gemspec
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/drsi/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = "drsi"
|
6
|
+
gem.version = Drsi::VERSION
|
7
|
+
gem.authors = ["Lorenzo Tello"]
|
8
|
+
gem.email = ["ltello8a@gmail.com"]
|
9
|
+
gem.homepage = "http://github.com/ltello/drsi"
|
10
|
+
gem.description = "Make DCI paradigm available to Ruby applications"
|
11
|
+
gem.summary = "Make DCI paradigm available to Ruby applications by enabling developers defining contexts subclassing the class DCI::Context. You define roles inside the definition. Match roles and player objects in context instantiation. Single Identity approach."
|
12
|
+
gem.licenses = ["MIT"]
|
13
|
+
|
14
|
+
gem.rubyforge_project = "drsi"
|
15
|
+
|
16
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
17
|
+
gem.files = `git ls-files`.split("\n")
|
18
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
gem.add_development_dependency "rspec", "~> 2.0"
|
23
|
+
# s.add_runtime_dependency "rest-client"
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
require 'drsi'
|
2
|
+
|
3
|
+
|
4
|
+
class CheckingAccount
|
5
|
+
attr_reader :account_id, :currency
|
6
|
+
attr_accessor :balance
|
7
|
+
|
8
|
+
def initialize(account_id, initial_balance)
|
9
|
+
@account_id = account_id
|
10
|
+
b, @currency = initial_balance.split(' ')
|
11
|
+
@balance = b.to_i
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class Amount
|
16
|
+
attr_reader :quantity, :currency
|
17
|
+
|
18
|
+
def initialize(data)
|
19
|
+
q, @currency = data.split(' ')
|
20
|
+
@quantity = q.to_i
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"#{quantity}#{currency}"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
class MoneyTransferContext < DCI::Context
|
30
|
+
|
31
|
+
# Roles Definitions
|
32
|
+
|
33
|
+
role :source_account do
|
34
|
+
def run_transfer
|
35
|
+
self.balance -= amount.quantity
|
36
|
+
puts "\t\tAccount(\##{account_id}) sent #{amount} to Account(\##{target_account.account_id})."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
role :target_account do
|
41
|
+
def run_transfer
|
42
|
+
self.balance += amount.quantity
|
43
|
+
puts "\t\tAccount(\##{account_id}) received #{amount} from Account(\##{source_account.account_id})."
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
role :amount
|
48
|
+
|
49
|
+
|
50
|
+
# Interactions
|
51
|
+
|
52
|
+
def run
|
53
|
+
puts "\nMoney Transfer of #{amount} between Account(\##{source_account.account_id}) and Account(\##{target_account.account_id})"
|
54
|
+
puts "\tBalances Before: #{balances}"
|
55
|
+
source_account.run_transfer
|
56
|
+
target_account.run_transfer
|
57
|
+
puts "\tBalances After: #{balances}"
|
58
|
+
end
|
59
|
+
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def accounts
|
64
|
+
[source_account, target_account]
|
65
|
+
end
|
66
|
+
|
67
|
+
def balances
|
68
|
+
accounts.map {|account| "#{account.balance}#{account.currency}"}.join(' - ')
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
acc1 = CheckingAccount.new(1, '1000 €')
|
73
|
+
acc2 = CheckingAccount.new(2, '0 €')
|
74
|
+
amount = Amount.new('200 €')
|
75
|
+
|
76
|
+
5.times do
|
77
|
+
MoneyTransferContext.new(:source_account => acc1,
|
78
|
+
:target_account => acc2,
|
79
|
+
:amount => amount).run
|
80
|
+
end
|
@@ -0,0 +1,170 @@
|
|
1
|
+
require 'drsi/dci/role'
|
2
|
+
|
3
|
+
module DCI
|
4
|
+
class Context
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
# Every subclass of Context has is own class and instance method roles defined.
|
9
|
+
# The instance method delegates value to the class.
|
10
|
+
def inherited(subklass)
|
11
|
+
subklass.class_eval do
|
12
|
+
@roles ||= {}
|
13
|
+
def self.roles; @roles end
|
14
|
+
def roles; self.class.roles end
|
15
|
+
private :roles
|
16
|
+
assign_unplay_roles_within_klass_instance_methods!(subklass)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# A short way for ContextSubclass.new(players_and_extra_args).run(extra_args)
|
21
|
+
def [](*args)
|
22
|
+
new(*args).run
|
23
|
+
end
|
24
|
+
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# The macro role is defined to allow a subclass of Context to define roles in its definition body.
|
29
|
+
# Every new role is added to the hash of roles in that Context subclass.
|
30
|
+
# A reader to access the object playing the new role is also defined and available in every instance of the context subclass.
|
31
|
+
# Also, readers to allow each other role access are defined.
|
32
|
+
def role(rolekey, &block)
|
33
|
+
raise "role name must be a symbol" unless rolekey.is_a?(Symbol)
|
34
|
+
create_role_from(rolekey, &block)
|
35
|
+
define_reader_for_role(rolekey)
|
36
|
+
define_mate_roleplayers_readers_after_newrole(rolekey)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Adds a new entry to the roles accumulator hash.
|
40
|
+
def create_role_from(key, &block)
|
41
|
+
roles.merge!(key => create_role_module_from(key, &block))
|
42
|
+
end
|
43
|
+
|
44
|
+
# Defines and return a new subclass of DCI::Role named after the given rolekey and with body the given block.
|
45
|
+
def create_role_module_from(rolekey, &block)
|
46
|
+
new_mod_name = rolekey.to_s.split(/\_+/).map(&:capitalize).join('')
|
47
|
+
const_set(new_mod_name, Module.new(&block))
|
48
|
+
const_get(new_mod_name).tap {|mod| mod.send(:extend, ::DCI::Role)}
|
49
|
+
end
|
50
|
+
|
51
|
+
# Defines a private reader to allow a context instance access to the roleplayer object associated to the given rolekey.
|
52
|
+
def define_reader_for_role(rolekey)
|
53
|
+
private
|
54
|
+
attr_reader rolekey
|
55
|
+
end
|
56
|
+
|
57
|
+
# After a new role is defined, you've got to create a reader method for this new role in the rest of context
|
58
|
+
# roles, and viceverse: create a reader method in the new role mod for each of the other roles in the context.
|
59
|
+
# This method does exactly this.
|
60
|
+
def define_mate_roleplayers_readers_after_newrole(new_rolekey)
|
61
|
+
new_role_mod = roles[new_rolekey]
|
62
|
+
mate_roles = mate_roles_of(new_rolekey)
|
63
|
+
mate_roles.each do |mate_rolekey, mate_role_mod|
|
64
|
+
mate_role_mod.send(:add_role_reader_for!, new_rolekey)
|
65
|
+
new_role_mod.send(:add_role_reader_for!, mate_rolekey)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# For a give role key, returns a hash with the rest of the roles (pair :rolekey => role_mod) in the context it belongs to.
|
70
|
+
def mate_roles_of(rolekey)
|
71
|
+
roles.dup.tap do |roles|
|
72
|
+
roles.delete(rolekey)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# Wraps every existing public/protected instance_method of klass (not superclasses methods) to assign roles to
|
77
|
+
# player objects before the execution and to un-assign roles from player objects at the end of execution just
|
78
|
+
# before returning control.
|
79
|
+
# Also inject code to magically do the same to every new method defined in klass.
|
80
|
+
def assign_unplay_roles_within_klass_instance_methods!(klass)
|
81
|
+
klass.instance_methods(false).each do |existing_methodname|
|
82
|
+
assign_unplay_roles_within_klass_instance_method!(klass, existing_methodname)
|
83
|
+
end
|
84
|
+
def klass.method_added(methodname)
|
85
|
+
if not @context_internals and public_method_defined?(methodname)
|
86
|
+
@context_internals = true
|
87
|
+
assign_unplay_roles_within_klass_instance_method!(self, methodname)
|
88
|
+
@context_internals = false
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# Wraps the given klass's methodname to assign/un-assign roles to player objects before and after actual method
|
94
|
+
# execution.
|
95
|
+
def assign_unplay_roles_within_klass_instance_method!(klass, methodname)
|
96
|
+
klass.class_eval do
|
97
|
+
method_object = instance_method(methodname)
|
98
|
+
define_method(methodname) do |*args, &block|
|
99
|
+
players_play_role!
|
100
|
+
method_object.bind(self).call(*args, &block).tap {players_unplay_role!}
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
|
108
|
+
# Instances of a defined subclass of Context are initialized checking first that all subclass defined roles
|
109
|
+
# are provided in the creation invocation raising an error if any of them is missing.
|
110
|
+
# Once the previous check is met, every object playing in the context instance is associated to the stated role.
|
111
|
+
# Non players args are associated to instance_variables and readers defined.
|
112
|
+
def initialize(args={})
|
113
|
+
check_all_roles_provided_in!(args)
|
114
|
+
players, noplayers = args.partition {|key, *| roles.has_key?(key)}.map {|group| Hash[*group.flatten]}
|
115
|
+
@_players = players
|
116
|
+
@settings = noplayers
|
117
|
+
end
|
118
|
+
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
# Private access to the extra args received in the instantiation.
|
123
|
+
# Returns a hash (copy of the instantiation extra args) with only the args included in 'keys' or all of them
|
124
|
+
# when called with no args.
|
125
|
+
def settings(*keys)
|
126
|
+
return @settings.dup if keys.empty?
|
127
|
+
entries = @settings.reject {|k, v| !keys.include?(k)}
|
128
|
+
keys.size == 1 ? entries.values.first : entries
|
129
|
+
end
|
130
|
+
|
131
|
+
# Checks there is a player for each role.
|
132
|
+
# Raises and error message in case of missing roles.
|
133
|
+
def check_all_roles_provided_in!(players={})
|
134
|
+
missing_rolekeys = missing_roles(players)
|
135
|
+
raise "missing roles #{missing_rolekeys}" unless missing_rolekeys.empty?
|
136
|
+
end
|
137
|
+
|
138
|
+
# The list of roles with no player provided
|
139
|
+
def missing_roles(players={})
|
140
|
+
(roles.keys - players.keys)
|
141
|
+
end
|
142
|
+
|
143
|
+
# Associates every role to the intended player.
|
144
|
+
def players_play_role!
|
145
|
+
roles.keys.each do |rolekey|
|
146
|
+
assign_role_to_player!(rolekey, @_players[rolekey])
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
# Associates a role to an intended player:
|
151
|
+
# - The player object is 'extended' with the methods of the role to play.
|
152
|
+
# - The player get access to the context it is playing.
|
153
|
+
# - The player get access to the rest of players in its context through instance methods named after their role keys.
|
154
|
+
# - This context instance get access to this new role player through an instance method named after the role key.
|
155
|
+
def assign_role_to_player!(rolekey, player)
|
156
|
+
role_mod = roles[rolekey]
|
157
|
+
player.__play_role!(role_mod, self)
|
158
|
+
instance_variable_set(:"@#{rolekey}", player)
|
159
|
+
end
|
160
|
+
|
161
|
+
# Disassociates every role from the playing object.
|
162
|
+
def players_unplay_role!
|
163
|
+
roles.keys.each do |rolekey|
|
164
|
+
@_players[rolekey].__unplay_last_role!
|
165
|
+
# 'instance_variable_set(:"@#{rolekey}", nil)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
end
|
170
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module DCI
|
2
|
+
module Role
|
3
|
+
|
4
|
+
private
|
5
|
+
|
6
|
+
def context
|
7
|
+
raise 'This method must be redefined in every module including DCI::Role'
|
8
|
+
end
|
9
|
+
|
10
|
+
# Defines a new private reader instance method for a context mate role, delegating it to the context object.
|
11
|
+
def add_role_reader_for!(rolekey)
|
12
|
+
return if private_method_defined?(rolekey)
|
13
|
+
private
|
14
|
+
define_method(rolekey) {context.send(rolekey)}
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
data/lib/drsi/module.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
class Module
|
2
|
+
|
3
|
+
# Define instance methods delegating execution to the corresponding ones in 'mod'.
|
4
|
+
def __copy_instance_methods_from(mod)
|
5
|
+
[:public_instance_methods, :protected_instance_methods, :private_instance_methods].each do |methods_type|
|
6
|
+
methods = mod.send(methods_type, false).map {|methodname| mod.instance_method(methodname)}
|
7
|
+
type = methods_type.to_s.split('_').first.to_sym
|
8
|
+
__add_instance_methods(methods, type)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
# Define instance methods binding to self the unbound ones received in 'methods'.
|
16
|
+
# Also, set their visibility from 'type' (:public, :protected, :private).
|
17
|
+
def __add_instance_methods(methods, type)
|
18
|
+
module_exec(methods, type) do |methods, type|
|
19
|
+
methods.each do |method|
|
20
|
+
define_method(method.name) do |*args, &block|
|
21
|
+
method.bind(self).call(*args, &block)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
send(type, *methods.map(&:name))
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
data/lib/drsi/object.rb
ADDED
data/lib/drsi/rolable.rb
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'drsi/module'
|
2
|
+
|
3
|
+
# This module defines a mechanism to extend and 'unextend' modules in an object.
|
4
|
+
#
|
5
|
+
# The idea is to provide an object with a heap of extended modules:
|
6
|
+
# the highest ones filled with the methods associated to the roles the object currently plays,
|
7
|
+
# and the lowest ones, clean (no methods) when the object finishes playing roles.
|
8
|
+
# reusing the empty ones or adding and extending new ones when it is needed.
|
9
|
+
module Rolable
|
10
|
+
|
11
|
+
# Make an object play the role defined as a module in 'mod'
|
12
|
+
def __play_role!(role_klass, context)
|
13
|
+
new_role = __next_empty_role
|
14
|
+
new_role.__copy_instance_methods_from(role_klass)
|
15
|
+
new_role.send(:define_method, :context) {context}
|
16
|
+
end
|
17
|
+
|
18
|
+
# Make an object stop playing the last role it plays, if any.
|
19
|
+
def __unplay_last_role!
|
20
|
+
if role = __last_role
|
21
|
+
methods = role.public_instance_methods(false) + role.protected_instance_methods(false) + role.private_instance_methods(false)
|
22
|
+
methods.each {|name| role.send(:remove_method, name)}
|
23
|
+
@__last_role_index = __last_role_index - 1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def __roles
|
31
|
+
@__roles ||= Array.new
|
32
|
+
end
|
33
|
+
|
34
|
+
def __last_role_index
|
35
|
+
@__last_role_index ||= -1
|
36
|
+
end
|
37
|
+
|
38
|
+
def __last_role
|
39
|
+
__roles[__last_role_index]
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the highest role module free of methods. If none, creates a new empty module ready to be filled with
|
43
|
+
# role instance methods.
|
44
|
+
def __next_empty_role
|
45
|
+
@__last_role_index = __last_role_index + 1
|
46
|
+
__add_empty_role! unless __last_role
|
47
|
+
__last_role
|
48
|
+
end
|
49
|
+
|
50
|
+
# Creates and extends a new module ready to be filled with role instance methods.
|
51
|
+
def __add_empty_role!
|
52
|
+
role = Module.new
|
53
|
+
extend(role)
|
54
|
+
__roles << role
|
55
|
+
end
|
56
|
+
|
57
|
+
# The context a role is played within. This method must be overidden in every __role definition module.
|
58
|
+
def context
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
|
62
|
+
# The role definition code also have private access to the extra args given in the context instantiation.
|
63
|
+
def settings(*keys)
|
64
|
+
context.send(:settings, *keys) if context
|
65
|
+
end
|
66
|
+
end
|
data/lib/drsi/version.rb
ADDED
data/lib/drsi.rb
ADDED
@@ -0,0 +1,117 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe DCI::Context do
|
4
|
+
|
5
|
+
context "Definition:" do
|
6
|
+
context "When Inheriting from DCI::Context..." do
|
7
|
+
before(:all) do
|
8
|
+
class TestingDefinitionContext < DCI::Context
|
9
|
+
role :role_name do
|
10
|
+
def role_name_method
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def interaction1
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it("...a new dci context is ready to be used...") {TestingDefinitionContext.superclass.should be(DCI::Context)}
|
20
|
+
it("...in which the developer can define roles...") do
|
21
|
+
TestingDefinitionContext.private_methods.map(&:to_s).should include("role")
|
22
|
+
end
|
23
|
+
it("...but privately inside the subclass.") {TestingDefinitionContext.should_not respond_to(:role)}
|
24
|
+
|
25
|
+
it("A role is defined calling the private macro #role with a role_key and a block defining the specific methods of the role.") do
|
26
|
+
TestingDefinitionContext.roles.size.should be(1)
|
27
|
+
TestingDefinitionContext.roles.keys.should include(:role_name)
|
28
|
+
end
|
29
|
+
|
30
|
+
it("The #roles public class_and_instance_method will return a hash with pairs (role_key => ContextSubclass::Rolekey)...") do
|
31
|
+
TestingDefinitionContext.roles.should eq({:role_name => TestingDefinitionContext::RoleName})
|
32
|
+
TestingDefinitionContext.new(:role_name => Object.new).send(:roles).should eq(:role_name => TestingDefinitionContext::RoleName)
|
33
|
+
end
|
34
|
+
it("... where every ContextSubclass::Rolekey is a new module created at load time,...") do
|
35
|
+
TestingDefinitionContext.roles[:role_name].should be_a(Module)
|
36
|
+
TestingDefinitionContext.roles[:role_name].should be(TestingDefinitionContext::RoleName)
|
37
|
+
end
|
38
|
+
it("... named after the associated role_key...") do
|
39
|
+
TestingDefinitionContext.const_defined?(:RoleName).should be(true)
|
40
|
+
end
|
41
|
+
it("... and defined after the block given to the associated role in its definition.") do
|
42
|
+
TestingDefinitionContext::RoleName.public_instance_methods.map(&:to_s).should include("role_name_method")
|
43
|
+
end
|
44
|
+
|
45
|
+
it("Inside the context subclass, the developer defines context methods (instance methods) that act as interactions.") do
|
46
|
+
TestingDefinitionContext.public_instance_methods(false).map(&:to_s).should include('interaction1')
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context "Use:" do
|
52
|
+
context "To use a Context..." do
|
53
|
+
before(:all) do
|
54
|
+
class TestingUseContext < DCI::Context
|
55
|
+
role :role1 do
|
56
|
+
def role1_method
|
57
|
+
:role1_method
|
58
|
+
end
|
59
|
+
end
|
60
|
+
role :role2 do
|
61
|
+
end
|
62
|
+
|
63
|
+
def run
|
64
|
+
role1.role1_method
|
65
|
+
end
|
66
|
+
|
67
|
+
def interaction2
|
68
|
+
role1
|
69
|
+
end
|
70
|
+
|
71
|
+
def interaction3
|
72
|
+
role1.object_id - role2.object_id
|
73
|
+
end
|
74
|
+
end
|
75
|
+
@player1, @player2 = Object.new, Object.new
|
76
|
+
@context_instance_1 = TestingUseContext.new(:role1 => @player1, :role2 => @player2)
|
77
|
+
@context_instance_2 = TestingUseContext.new(:role1 => @player1, :role2 => @player2, :extra_arg => :extra)
|
78
|
+
end
|
79
|
+
|
80
|
+
it("...instanciate it from its correspondig DCI::Context subclass as usual...") do
|
81
|
+
@context_instance_1.should be_a(TestingUseContext)
|
82
|
+
end
|
83
|
+
it("...providing pairs of type :rolekey1 => player1 as arguments...") do
|
84
|
+
expect {TestingUseContext.new(@player1, @player2)}.to raise_error
|
85
|
+
end
|
86
|
+
it("...with ALL the role_keys in the subclass as keys...") do
|
87
|
+
expect {TestingUseContext.new(:role1 => @player1)}.to raise_error(/missing roles(.+)role2/)
|
88
|
+
end
|
89
|
+
it("...and the objects to play those roles as values.") do
|
90
|
+
@context_instance_1.interaction2
|
91
|
+
[@player1, @player2].should include(@context_instance_1.send(:role1), @context_instance_1.send(:role2))
|
92
|
+
end
|
93
|
+
|
94
|
+
it("...You can also include other extra pairs as arguments...") do
|
95
|
+
expect {TestingUseContext.new(:role1 => @player1, :role2 => @player2, :extra_arg => :extra)}.not_to raise_error
|
96
|
+
end
|
97
|
+
|
98
|
+
it("A shorter way to instantiate a context is through the class method []...") do
|
99
|
+
expect {TestingUseContext[:role1 => @player1, :role2 => @player2, :extra_arg => :extra]}.not_to raise_error
|
100
|
+
end
|
101
|
+
it("...which is equivalent to create a new instance and call #run on it.") do
|
102
|
+
TestingUseContext[:role1 => @player1, :role2 => @player2].should be(:role1_method)
|
103
|
+
end
|
104
|
+
|
105
|
+
|
106
|
+
it("Once instantiated...") {@context_instance_1.should be_a(TestingUseContext)}
|
107
|
+
it("...you call an interaction (instance method) on it") do
|
108
|
+
@context_instance_1.should respond_to(:interaction2)
|
109
|
+
end
|
110
|
+
it("...to start interaction among roleplayers inside the context.") do
|
111
|
+
@context_instance_1.interaction3.should be_instance_of(Fixnum)
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe 'Interaction:' do
|
4
|
+
|
5
|
+
context "Inside a Context instance method(interaction)..." do
|
6
|
+
before(:all) do
|
7
|
+
class TestingInteractionsContext < DCI::Context
|
8
|
+
role :role1 do
|
9
|
+
end
|
10
|
+
role :role2 do
|
11
|
+
end
|
12
|
+
|
13
|
+
def interaction1
|
14
|
+
role1
|
15
|
+
end
|
16
|
+
end
|
17
|
+
@player1, @player2 = Object.new, Object.new
|
18
|
+
@test_interactions_context = TestingInteractionsContext.new(:role1 => @player1,
|
19
|
+
:role2 => @player2,
|
20
|
+
:setting1 => :one,
|
21
|
+
:setting2 => :two,
|
22
|
+
:setting3 => :three)
|
23
|
+
end
|
24
|
+
|
25
|
+
it("...the developer has access to all the roleplayers...") do
|
26
|
+
@test_interactions_context.interaction1.should be(@player1)
|
27
|
+
@test_interactions_context.send(:role2).should be(@player2)
|
28
|
+
end
|
29
|
+
it("...via private instance methods named after their role keys.") do
|
30
|
+
@test_interactions_context.private_methods(false).map(&:to_s).should include('role1', 'role2')
|
31
|
+
end
|
32
|
+
|
33
|
+
it("He also have private access to extra args received in the instantiation of its context...") do
|
34
|
+
@test_interactions_context.private_methods.map(&:to_s).should include('settings')
|
35
|
+
@test_interactions_context.public_methods.map(&:to_s).should_not include('settings')
|
36
|
+
end
|
37
|
+
it("...calling #settings that returns a hash with all the extra args...") do
|
38
|
+
@test_interactions_context.send(:settings).should eq({:setting1 => :one, :setting2 => :two, :setting3 => :three})
|
39
|
+
end
|
40
|
+
it("...or #settings(key) that returns the value of the given extra arg...") do
|
41
|
+
@test_interactions_context.send(:settings, :setting2).should be(:two)
|
42
|
+
end
|
43
|
+
it("...or #settings(key1, key2, ...) that returns a hash with the given extra args.") do
|
44
|
+
@test_interactions_context.send(:settings, :setting1, :setting3).should eq({:setting1 => :one, :setting3 => :three})
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
# require 'ostruct'
|
3
|
+
|
4
|
+
|
5
|
+
describe 'Players:' do
|
6
|
+
|
7
|
+
class CheckingAccount
|
8
|
+
attr_reader :account_id
|
9
|
+
attr_accessor :balance
|
10
|
+
|
11
|
+
def initialize(account_id, initial_balance=0)
|
12
|
+
@account_id, @balance = account_id, initial_balance
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
class MoneyTransferContext < DCI::Context
|
18
|
+
|
19
|
+
# Roles Definitions
|
20
|
+
role :source_account do
|
21
|
+
def run_transfer_of(amount)
|
22
|
+
self.balance -= amount
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
role :target_account do
|
27
|
+
def run_transfer_of(amount)
|
28
|
+
self.balance += amount
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Interactions
|
33
|
+
def run(amount=settings(:amount))
|
34
|
+
source_account.run_transfer_of(amount)
|
35
|
+
target_account.run_transfer_of(amount)
|
36
|
+
balances
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def accounts
|
41
|
+
[source_account, target_account]
|
42
|
+
end
|
43
|
+
|
44
|
+
def balances
|
45
|
+
accounts.map(&:balance)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
context "Are common Ruby objects that play roles inside contexts:" do
|
50
|
+
before(:all) do
|
51
|
+
@account1 = CheckingAccount.new(1, 1000)
|
52
|
+
@account2 = CheckingAccount.new(2)
|
53
|
+
@account1_public_interface = @account1.public_methods
|
54
|
+
end
|
55
|
+
|
56
|
+
context "Before becoming roleplayers inside a context..." do
|
57
|
+
it("...they are in an initial state...") do
|
58
|
+
@account1.balance.should be(1000)
|
59
|
+
@account2.balance.should be(0)
|
60
|
+
end
|
61
|
+
it("...and have got a given public interface.") do
|
62
|
+
@account1_public_interface.should be_true
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
context "After playing a role inside a context..." do
|
67
|
+
before(:all) do
|
68
|
+
MoneyTransferContext.new(:source_account => @account1,
|
69
|
+
:target_account => @account2).run(200)
|
70
|
+
end
|
71
|
+
it("...they still preserve their public interface...") do
|
72
|
+
@account1.public_methods.should eql(@account1_public_interface)
|
73
|
+
@account1.should_not respond_to(:run_transfer_of)
|
74
|
+
@account1.private_methods.should_not include(:run_transfer_of)
|
75
|
+
end
|
76
|
+
it("...although their state might have been changed!") do
|
77
|
+
@account1.balance.should_not be(1000)
|
78
|
+
@account2.balance.should_not be(0)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
data/spec/role_spec.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
|
3
|
+
describe 'Role' do
|
4
|
+
|
5
|
+
context "When defining roles inside a DCI::Context subclass..." do
|
6
|
+
before(:all) do
|
7
|
+
class TestingRoleContext < DCI::Context
|
8
|
+
role :rolename do
|
9
|
+
end
|
10
|
+
role :anotherrolename do
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
it("...you can define as many as you want.") do
|
15
|
+
TestingRoleContext.roles.keys.size.should eql(2)
|
16
|
+
end
|
17
|
+
it("Each rolename must be provided as a symbol...") do
|
18
|
+
TestingRoleContext.roles.keys.should include(:rolename, :anotherrolename)
|
19
|
+
end
|
20
|
+
it("...and not as a string.") do
|
21
|
+
expect do
|
22
|
+
class TestingRoleContext < DCI::Context
|
23
|
+
role "rolename" do
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end.to raise_error
|
27
|
+
end
|
28
|
+
it("A block defining rolemethods must be provided as well.") do
|
29
|
+
TestingRoleContext.roles[:rolename].should be_a(Module)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,107 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
2
|
+
require 'ostruct'
|
3
|
+
|
4
|
+
describe 'RolePlayers' do
|
5
|
+
|
6
|
+
context "Are Ruby objects inside a context instance..." do
|
7
|
+
before(:all) do
|
8
|
+
class TestingRoleplayersContext < DCI::Context
|
9
|
+
role :role1 do
|
10
|
+
def role1method1; :role1method1_executed end
|
11
|
+
def role1self; self end
|
12
|
+
end
|
13
|
+
|
14
|
+
role :role2 do
|
15
|
+
def role2method1; role1 end
|
16
|
+
def role2self; self end
|
17
|
+
|
18
|
+
private
|
19
|
+
def private_role2method2; :private_rolemethod_return_value end
|
20
|
+
end
|
21
|
+
|
22
|
+
def check_role_interaccess; role2.role2method1 == role1 end
|
23
|
+
|
24
|
+
def check_role1_identity(obj)
|
25
|
+
[role1 == obj, role1.role1self == obj, role1.respond_to?(:role1method1), obj.respond_to?(:role1method1),
|
26
|
+
role1.role1method1 == :role1method1_executed, obj.role1method1 == :role1method1_executed].uniq == [true]
|
27
|
+
end
|
28
|
+
|
29
|
+
def check_role2_identity(obj)
|
30
|
+
[role2 == obj, role2.role2self == obj, role2.respond_to?(:role2method1), obj.respond_to?(:role2method1),
|
31
|
+
role2.send(:private_role2method2) == :private_rolemethod_return_value,
|
32
|
+
obj.send(:private_role2method2) == :private_rolemethod_return_value,
|
33
|
+
role2.role2method1 == role1, obj.role2method1 == role1].uniq == [true]
|
34
|
+
end
|
35
|
+
|
36
|
+
def access_role1_external_interface
|
37
|
+
role1.name
|
38
|
+
end
|
39
|
+
|
40
|
+
def check_role1_context_access
|
41
|
+
role1.context == self
|
42
|
+
end
|
43
|
+
|
44
|
+
def check_role1_settings_access
|
45
|
+
[!role1.respond_to?(:settings), role1.private_methods.map(&:to_s).include?('settings'),
|
46
|
+
role1.send(:settings) == settings].uniq == [true]
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
@player1, @player2 = OpenStruct.new(:name => 'player1'), OpenStruct.new(:name => 'player2')
|
51
|
+
@testing_roleplayers_context = TestingRoleplayersContext.new(:role1 => @player1,
|
52
|
+
:role2 => @player2,
|
53
|
+
:setting1 => :one,
|
54
|
+
:setting2 => :two,
|
55
|
+
:setting3 => :three)
|
56
|
+
end
|
57
|
+
|
58
|
+
it("...that adquire the public instance methods defined in their role...") do
|
59
|
+
@testing_roleplayers_context.check_role1_identity(@player1).should be_true
|
60
|
+
end
|
61
|
+
it("...as well as the private ones.") do
|
62
|
+
@testing_roleplayers_context.check_role2_identity(@player2).should be_true
|
63
|
+
end
|
64
|
+
|
65
|
+
it("They still preserve their identity") do
|
66
|
+
@testing_roleplayers_context.check_role1_identity(@player1).should be_true
|
67
|
+
end
|
68
|
+
it("...and therefore, their state and behaviour are accessible inside the context.") do
|
69
|
+
@testing_roleplayers_context.access_role1_external_interface.should eq('player1')
|
70
|
+
end
|
71
|
+
|
72
|
+
it("Inside the context, roleplayers have private access to other roleplayers through methods named after their keys.") do
|
73
|
+
@testing_roleplayers_context.check_role_interaccess.should be_true
|
74
|
+
end
|
75
|
+
it("...and offer a public_method to access the context.") do
|
76
|
+
@testing_roleplayers_context.check_role1_context_access.should be_true
|
77
|
+
end
|
78
|
+
|
79
|
+
it("They also have private access to extra args received in the instantiation of its context...") do
|
80
|
+
@testing_roleplayers_context.check_role1_settings_access.should be_true
|
81
|
+
end
|
82
|
+
it("...calling #settings that returns a hash with all the extra args...") do
|
83
|
+
@testing_roleplayers_context.send(:settings).should eq({:setting1 => :one, :setting2 => :two, :setting3 => :three})
|
84
|
+
end
|
85
|
+
it("...or #settings(key) that returns the value of the given extra arg...") do
|
86
|
+
@testing_roleplayers_context.send(:settings, :setting2).should be(:two)
|
87
|
+
end
|
88
|
+
it("...or #settings(key1, key2, ...) that returns a hash with the given extra args.") do
|
89
|
+
@testing_roleplayers_context.send(:settings, :setting1, :setting3).should eq({:setting1 => :one, :setting3 => :three})
|
90
|
+
end
|
91
|
+
|
92
|
+
it("But all these features, are only inside a context. Never out of it!") do
|
93
|
+
@player1.should_not respond_to(:role1method1)
|
94
|
+
@player2.private_methods.map(&:to_s).should_not include(:private_role2method2)
|
95
|
+
@player1.name.should eq('player1')
|
96
|
+
@player2.should_not respond_to(:role1)
|
97
|
+
@player2.should_not respond_to(:context)
|
98
|
+
@player1.should_not respond_to(:settings)
|
99
|
+
@player1.private_methods.map(&:to_s).should include('settings')
|
100
|
+
@player1.send(:settings).should be_nil
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
3
|
+
require 'rspec'
|
4
|
+
require 'drsi'
|
5
|
+
|
6
|
+
# Requires supporting files with custom matchers and macros, etc,
|
7
|
+
# in ./support/ and its subdirectories.
|
8
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
9
|
+
|
10
|
+
RSpec.configure do |config|
|
11
|
+
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: drsi
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lorenzo Tello
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-01-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.0'
|
27
|
+
description: Make DCI paradigm available to Ruby applications
|
28
|
+
email:
|
29
|
+
- ltello8a@gmail.com
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".gitignore"
|
35
|
+
- ".rspec"
|
36
|
+
- ".ruby-gemset"
|
37
|
+
- ".ruby-version"
|
38
|
+
- Gemfile
|
39
|
+
- LICENSE
|
40
|
+
- README.md
|
41
|
+
- Rakefile
|
42
|
+
- drsi.gemspec
|
43
|
+
- examples/money_transfer.rb
|
44
|
+
- lib/drsi.rb
|
45
|
+
- lib/drsi/dci/context.rb
|
46
|
+
- lib/drsi/dci/role.rb
|
47
|
+
- lib/drsi/module.rb
|
48
|
+
- lib/drsi/object.rb
|
49
|
+
- lib/drsi/rolable.rb
|
50
|
+
- lib/drsi/version.rb
|
51
|
+
- spec/context_spec.rb
|
52
|
+
- spec/interaction_spec.rb
|
53
|
+
- spec/players_spec.rb
|
54
|
+
- spec/role_spec.rb
|
55
|
+
- spec/roleplayers_spec.rb
|
56
|
+
- spec/spec_helper.rb
|
57
|
+
homepage: http://github.com/ltello/drsi
|
58
|
+
licenses:
|
59
|
+
- MIT
|
60
|
+
metadata: {}
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project: drsi
|
77
|
+
rubygems_version: 2.2.1
|
78
|
+
signing_key:
|
79
|
+
specification_version: 4
|
80
|
+
summary: Make DCI paradigm available to Ruby applications by enabling developers defining
|
81
|
+
contexts subclassing the class DCI::Context. You define roles inside the definition.
|
82
|
+
Match roles and player objects in context instantiation. Single Identity approach.
|
83
|
+
test_files:
|
84
|
+
- spec/context_spec.rb
|
85
|
+
- spec/interaction_spec.rb
|
86
|
+
- spec/players_spec.rb
|
87
|
+
- spec/role_spec.rb
|
88
|
+
- spec/roleplayers_spec.rb
|
89
|
+
- spec/spec_helper.rb
|