dci-ruby 2.0.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -34,34 +34,37 @@ dci-ruby gives you the class DCI::Context to inherit from to create your own con
34
34
  end
35
35
 
36
36
  # Interactions
37
- def run(amount=default_amount)
37
+ def run(amount=settings(:amount))
38
38
  source_account.transfer(amount)
39
39
  target_account.get_transfer(amount)
40
40
  end
41
41
  end
42
42
 
43
43
  Every context defines some roles to be played by external objects (players) and their interactions. This way
44
- you have all the agents and operations in a user case wrapped in just one entity instead of spreaded throughout the
44
+ you have all the agents and operations in a user case wrapped in just one entity instead of spread out throughout the
45
45
  application code.
46
46
 
47
- Use the defined contexts, instantiating them wherever you need in your code:
47
+ Use the defined contexts, instantiating them, wherever you need in your code:
48
48
 
49
49
  MoneyTransfer.new(:source_account => Account.new(1),
50
50
  :target_account => Account.new(2)).run(100)
51
51
 
52
- In a context instance, every role instanciates an object (roleplayer) that gathers the behaviour defined inside its role,
53
- and that roleplayer object has private access to the original object adopting the role (player): the Account instances above are players.
52
+ In a context instance, every role instantiates an object (roleplayer) that gathers the behaviour defined inside its role,
53
+ and has private access to the original object adopting the role (player): the Account instances above are players.
54
54
  Instances of MoneyTransfer::SourceAccount and MoneyTransfer::TargetAccount accessible inside MoneyTransfer.new via
55
55
  the private instance_methods #source_account and #target_account are roleplayers.
56
56
  Also, every roleplayer has private access to the rest of roleplayers in its context.
57
57
 
58
+ Unlike extending players with role modules this Presenter approach gets on well with ruby method call caching mechanism.
59
+ (see Tony Arcieri's article {DCI in Ruby is completely broken}[http://tonyarcieri.com/dci-in-ruby-is-completely-broken])
60
+
58
61
  When instanciating a Context, the extra no-role pairs given as arguments are read-only attributes accessible inside the instance:
59
62
 
60
63
  MoneyTransfer.new(:source_account => Account.new(1),
61
64
  :target_account => Account.new(2),
62
- :default_amount => 500).run
65
+ :amount => 500).run
63
66
 
64
- here, default_amount is not a player (has no associated role) but is still accessible in the interactions.
67
+ here, :amount is not a player (has no associated role) but is still privately accessible in the interactions via settings(:amount).
65
68
 
66
69
  See the examples[https://github.com/ltello/dci-ruby/tree/master/examples] folder for examples of use and the DCI-Sample[https://github.com/ltello/DCI-Sample] repository for a sample application using DCI through this gem.
67
70
 
@@ -42,7 +42,7 @@ class MoneyTransferContext < DCI::Context
42
42
 
43
43
  # Interactions
44
44
 
45
- def run
45
+ def run(amount=settings(:amount))
46
46
  puts "Balances Before: #{balances}"
47
47
  source_account.run_transfer_of(amount)
48
48
  target_account.run_transfer_of(amount)
@@ -61,6 +61,13 @@ class MoneyTransferContext < DCI::Context
61
61
  end
62
62
  end
63
63
 
64
- MoneyTransferContext.new(:source_account => CheckingAccount.new(1, 500),
65
- :target_account => CheckingAccount.new(2),
66
- :amount => 500).run
64
+ acc1 = CheckingAccount.new(1, 1000)
65
+ acc2 = CheckingAccount.new(2)
66
+
67
+ MoneyTransferContext.new(:source_account => acc1,
68
+ :target_account => acc2,
69
+ :amount => 200).run
70
+ 4.times do
71
+ MoneyTransferContext.new(:source_account => acc1,
72
+ :target_account => acc2).run(200)
73
+ end
@@ -19,16 +19,51 @@ module DCI
19
19
 
20
20
  private
21
21
 
22
- # The macro role is defined to allow a subclass of Context to define roles in its definition.
23
- # Every new role redefines the role class method to contain a hash accumulating all defined roles in that subclass.
24
- # An accessor to the object playing the new role is also defined and available in every instance of the context subclass.
25
- def role(role_key, &block)
26
- raise "role name must be a symbol" unless role_key.is_a?(Symbol)
27
- new_klass_name = role_key.to_s.split(/\_+/).map(&:capitalize).join('')
22
+ # The macro role is defined to allow a subclass of Context to define roles in its definition body.
23
+ # Every new role is added to the hash of roles in that Context subclass.
24
+ # A reader to access the object playing the new role is also defined and available in every instance of the context subclass.
25
+ # Also, readers to allow each other role access are defined.
26
+ def role(rolekey, &block)
27
+ raise "role name must be a symbol" unless rolekey.is_a?(Symbol)
28
+ create_role_from(rolekey, &block)
29
+ define_reader_for_role(rolekey)
30
+ define_mate_roleplayers_readers_after_newrole(rolekey)
31
+ end
32
+
33
+ # Adds a new entry to the roles accumulator hash.
34
+ def create_role_from(key, &block)
35
+ roles.merge!(key => create_role_subclass_from(key, &block))
36
+ end
37
+
38
+ # Defines and return a new subclass of DCI::Role named after the given rolekey and with body the given block.
39
+ def create_role_subclass_from(rolekey, &block)
40
+ new_klass_name = rolekey.to_s.split(/\_+/).map(&:capitalize).join('')
28
41
  const_set(new_klass_name, Class.new(::DCI::Role, &block))
29
- roles.merge!(role_key => const_get(new_klass_name.to_sym))
30
- attr_reader role_key
31
- private role_key
42
+ end
43
+
44
+ # Defines a private reader to allow a context instance access to the roleplayer object associated to the given rolekey.
45
+ def define_reader_for_role(rolekey)
46
+ attr_reader rolekey
47
+ private rolekey
48
+ end
49
+
50
+ # After a new role is defined, you've got to create a reader method for this new role in the rest of context
51
+ # roles, and viceverse: create a reader method in the new role klass for each of the other roles in the context.
52
+ # This method does exactly this.
53
+ def define_mate_roleplayers_readers_after_newrole(new_rolekey)
54
+ new_roleklass = roles[new_rolekey]
55
+ mate_roles = mate_roles_of(new_rolekey)
56
+ mate_roles.each do |mate_rolekey, mate_roleklass|
57
+ mate_roleklass.add_role_reader_for!(new_rolekey)
58
+ new_roleklass.add_role_reader_for!(mate_rolekey)
59
+ end
60
+ end
61
+
62
+ # For a give role key, returns a hash with the rest of the roles (pair :rolekey => roleklass) in the context it belongs to.
63
+ def mate_roles_of(rolekey)
64
+ roles.dup.tap do |roles|
65
+ roles.delete(rolekey)
66
+ end
32
67
  end
33
68
 
34
69
  end
@@ -40,19 +75,24 @@ module DCI
40
75
  # Non players args are associated to instance_variables and readers defined.
41
76
  def initialize(args={})
42
77
  check_all_roles_provided_in!(args)
43
- players, noplayers = args.partition {|key, value| roles.has_key?(key)}.map {|group| Hash[*group.flatten]}
78
+ players, noplayers = args.partition {|key, *| roles.has_key?(key)}.map {|group| Hash[*group.flatten]}
44
79
  assign_roles_to_players(players)
45
- define_readers_for_no_players(noplayers)
80
+ @settings = noplayers
46
81
  end
47
82
 
48
83
 
49
84
  private
50
85
 
86
+ # Private access to the extra args received in the instantiation.
87
+ def settings(key)
88
+ @settings[key]
89
+ end
90
+
51
91
  # Checks there is a player for each role.
52
92
  # Raises and error message in case of missing roles.
53
93
  def check_all_roles_provided_in!(players={})
54
- missing_roles = missing_roles(players)
55
- raise "missing roles #{missing_roles}" unless missing_roles.empty?
94
+ missing_rolekeys = missing_roles(players)
95
+ raise "missing roles #{missing_rolekeys}" unless missing_rolekeys.empty?
56
96
  end
57
97
 
58
98
  # The list of roles with no player provided
@@ -62,33 +102,32 @@ module DCI
62
102
 
63
103
  # Associates every role to the intended player.
64
104
  def assign_roles_to_players(players={})
65
- roles.keys.each do |role_key|
66
- assign_role_to_player(role_key, players[role_key])
105
+ roles.keys.each do |rolekey|
106
+ assign_role_to_player(rolekey, players[rolekey])
67
107
  end
68
108
  end
69
109
 
70
110
  # Associates a role to an intended player:
71
- # - A new role instance is created from the associated role_key class and the player to get that role.
111
+ # - A new role instance is created from the associated rolekey class and the player to get that role.
72
112
  # - The new role instance has access to the context it is playing.
73
113
  # - The new role instance has access to the rest of players in its context through instance methods named after their role keys.
74
114
  # - The context instance has access to this new role instance through an instance method named after the role key.
75
- def assign_role_to_player(role_key, player)
76
- role_klass = roles[role_key]
77
- other_role_keys = roles.keys - [role_key]
78
- role_instance = role_klass.new(:player => player, :context => self, :role_mate_keys => other_role_keys)
79
- instance_variable_set(:"@#{role_key}", role_instance)
115
+ def assign_role_to_player(rolekey, player)
116
+ role_klass = roles[rolekey]
117
+ role_instance = role_klass.new(:player => player, :context => self)
118
+ instance_variable_set(:"@#{rolekey}", role_instance)
80
119
  end
81
120
 
82
- # For each given pair in vars, define a private method named the key that returns the entry associated value.
83
- def define_readers_for_no_players(vars={})
84
- vars.each do |name, value|
85
- instance_variable_set(:"@#{name}", value)
86
- singleton_class.class_exec(name.to_sym) do |varkey|
87
- private
88
- attr_reader varkey
89
- end
90
- end
91
- end
121
+ # # For each given pair in vars, define a private method named the key that returns the entry associated value.
122
+ # def define_readers_for_no_players(vars={})
123
+ # vars.each do |name, value|
124
+ # instance_variable_set(:"@#{name}", value)
125
+ # singleton_class.class_exec(name.to_sym) do |varkey|
126
+ # private
127
+ # attr_reader varkey
128
+ # end
129
+ # end
130
+ # end
92
131
 
93
132
  end
94
133
 
@@ -10,6 +10,13 @@ module DCI
10
10
  super
11
11
  end
12
12
 
13
+ # Defines a new reader instance method for a context mate role, delegating it to the context object.
14
+ def add_role_reader_for!(rolekey)
15
+ return if private_method_defined?(rolekey)
16
+ define_method(rolekey) {@context.send(rolekey)}
17
+ private rolekey
18
+ end
19
+
13
20
  end
14
21
 
15
22
  # Opts:
@@ -19,7 +26,6 @@ module DCI
19
26
  def initialize(opts={})
20
27
  @player = opts[:player]
21
28
  @context = opts[:context]
22
- define_role_mate_methods!(opts[:role_mate_keys])
23
29
  end
24
30
 
25
31
 
@@ -30,20 +36,5 @@ module DCI
30
36
  @player
31
37
  end
32
38
 
33
- # For each role in the context, define a method so inside a role you can access all the others.
34
- def define_role_mate_methods!(role_mate_keys)
35
- self.class.class_exec(role_mate_keys) do |other_role_keys|
36
- other_role_keys.each do |role_key|
37
- if not private_method_defined?(role_key)
38
- define_method(role_key) do
39
- @context.send(role_key)
40
- end
41
- private role_key
42
- end
43
- end
44
- end
45
- end
46
-
47
39
  end
48
-
49
40
  end
@@ -1,3 +1,3 @@
1
1
  module DCI
2
- VERSION = "2.0.0"
2
+ VERSION = "2.1.0"
3
3
  end
@@ -15,7 +15,7 @@ describe 'Interaction:' do
15
15
  end
16
16
  end
17
17
  @player1, @player2 = Object.new, Object.new
18
- @test_interactions_context = TestingInteractionsContext.new(:role1 => @player1, :role2 => @player2, :extra_arg => :extra)
18
+ @test_interactions_context = TestingInteractionsContext.new(:role1 => @player1, :role2 => @player2, :setting1 => :one)
19
19
  end
20
20
 
21
21
  it("...the developer has access to all the roleplayers...") do
@@ -26,11 +26,11 @@ describe 'Interaction:' do
26
26
  @test_interactions_context.private_methods(false).should include('role1', 'role2')
27
27
  end
28
28
 
29
- it("He also have access to extra args received in the instantiation of its context...") do
30
- @test_interactions_context.send(:extra_arg).should be(:extra)
29
+ it("He also have private access to extra args received in the instantiation of its context...") do
30
+ @test_interactions_context.private_methods.should include('settings')
31
31
  end
32
- it("...through private methods named after their keys.") do
33
- @test_interactions_context.private_methods(false).should include('extra_arg')
32
+ it("...calling #settings(key)") do
33
+ @test_interactions_context.send(:settings, :setting1).should be(:one)
34
34
  end
35
35
  end
36
36
 
@@ -63,11 +63,11 @@ describe 'RolePlayers' do
63
63
  @testing_roleplayers_context.send(:role2).send(:player).name.should eq('player2')
64
64
  end
65
65
 
66
- it("Roleplayers have private access to other roleplayers in their context...") do
67
- @testing_roleplayers_context.send(:role2).private_methods(false).should include('role1')
68
- @testing_roleplayers_context.send(:role2).rolemethod2.should eql(@testing_roleplayers_context.send(:role1))
66
+ it("Roleplayers have private access to other roleplayers in their context through methods named after their keys.") do
67
+ @testing_roleplayers_context.send(:role2).private_methods.should include('role1')
68
+ @testing_roleplayers_context.send(:role2).rolemethod2.should be(@testing_roleplayers_context.send(:role1))
69
69
  end
70
- it("...and even the context itself.") do
70
+ it("However, they dont have a method to access the context.") do
71
71
  expect {@testing_roleplayers_context.send(:role2).send(:context)}.to raise_error(NoMethodError)
72
72
  @testing_roleplayers_context.send(:role2).instance_variables.should include('@context')
73
73
  @testing_roleplayers_context.send(:role2).instance_variable_get(:@context).should be(@testing_roleplayers_context)
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dci-ruby
3
3
  version: !ruby/object:Gem::Version
4
- hash: 15
4
+ hash: 11
5
5
  prerelease:
6
6
  segments:
7
7
  - 2
8
+ - 1
8
9
  - 0
9
- - 0
10
- version: 2.0.0
10
+ version: 2.1.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Lorenzo Tello