drsi 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format nested
data/.ruby-gemset ADDED
@@ -0,0 +1 @@
1
+ drsi
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ ruby-2.1.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in drsi.gemspec
4
+ gemspec
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
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
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
@@ -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
@@ -0,0 +1,6 @@
1
+ require 'drsi/rolable'
2
+
3
+ class Object
4
+ include Rolable
5
+ end
6
+
@@ -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
@@ -0,0 +1,3 @@
1
+ module Drsi
2
+ VERSION = "0.0.1"
3
+ end
data/lib/drsi.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'drsi/version'
2
+ require 'drsi/object'
3
+ require 'drsi/dci/context'
4
+
5
+ module Drsi
6
+ end
@@ -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
@@ -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