surrounded 1.1.1 → 1.1.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e6cd923340dae45ac571dd98abf41be26d111f4569c59d98c27cd4e71f6204d1
4
- data.tar.gz: a5d11c76eccb01b30fdec8aaced90571449d96c21fe4eced13e55dc1603241da
3
+ metadata.gz: aa24dd1e23a33e7949fe503496f6ef64d86cccc0ac3abd70e149acc9cc24ac44
4
+ data.tar.gz: d282d6bdf295c8a0d9a08a0e43b679902a355f9a556dd9a2504261fbbecfa430
5
5
  SHA512:
6
- metadata.gz: 90659aa84b6565797f5b45fb4b637d0dfa21190dfed5366c20b586c4351ca1d0f02fdc5f8d6c45620ac6c9b36dccac28a842ce74b93a2d0a9258297db3d9e0e4
7
- data.tar.gz: 4831ec1fe6a7dda8a9578776ecb0b2e2030ff37490d15f7e7f35351253bfe7f2c52c9ae1aa8fb7172cb471236b3d5c639f7eae8ef8c5510668a56542ba0fe014
6
+ metadata.gz: ea3dcc682bc3c6b3e5bc0c1be6e68fc4c2e920a0a25d5e8d12ee91ecd726ac2c812a3539576e2db1838d59fb65a9fdb18c9b65f5dd7dffeeeac1bf2f1c646a18
7
+ data.tar.gz: 3251756a4d7bd37979a8b8af55867b47d1cffdfabb1503ab641f1c756f22b8867c9e08f997e4a3a0582ff96d0b288a1dee1cb91e479f467d212925893ca838ac
data/Changelog.md CHANGED
@@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](http://keepachangelog.com/)
6
6
  and this project adheres to [Semantic Versioning](http://semver.org/).
7
7
 
8
+ ## [1.1.2] - 2026-06-24
9
+
10
+ ### Fixed
11
+
12
+ - Roles applied through a wrapper, such as an interface, can reach the other roles in their context. (c1d41a6)
13
+
8
14
  ## [1.1.1] - 2025-10-17
9
15
 
10
16
  ### Changed
@@ -46,11 +46,8 @@ module Surrounded
46
46
  # Create an object which will bind methods to the role player.
47
47
  #
48
48
  # This object will behave differently that a wrapper or delegate_class.
49
- # The interface object should only be used for objects whose methods
50
- # _will not_ call to the other objects in the context.
51
- # Because the interface methods are applied individually to an object,
52
- # that object is unaware of the other objects in the context and cannot
53
- # access them from any of its methods.
49
+ # Interface methods run as the wrapped object itself, so they can access
50
+ # the other objects in the context only if that object includes Surrounded.
54
51
  def interface(name, &block)
55
52
  # AdminInterface
56
53
  interface_name = RoleName(name, "Interface")
@@ -32,17 +32,41 @@ module Surrounded
32
32
  keys.include?(role)
33
33
  end
34
34
 
35
- # Check if an object is playing a role in this map
35
+ # Record the behaviored player applied to a role for the duration of a
36
+ # trigger. The assigned domain object stays in the container; the player
37
+ # (a wrapper, or the same object for cast roles) is tracked here.
38
+ def apply(role, player)
39
+ applied[role] = player
40
+ end
41
+
42
+ # The player a role currently presents: the applied player while a trigger
43
+ # is running, otherwise the assigned domain object.
44
+ def current_player(role)
45
+ applied.fetch(role) { assigned_player(role) }
46
+ end
47
+
48
+ # Forget all applied players, e.g. after a trigger removes behaviors.
49
+ def reset_applied
50
+ applied.clear
51
+ end
52
+
53
+ # Check if an object is playing a role in this map, by identity — whether
54
+ # it is the assigned domain object or the applied player wrapping it.
36
55
  def role_player?(object)
37
- !values(object).empty?
38
- rescue container.class::ItemNotPresent
39
- false
56
+ values.any? { |player| player.equal?(object) } ||
57
+ applied.values.any? { |player| player.equal?(object) }
40
58
  end
41
59
 
42
- # Get the object playing the given role
60
+ # Get the domain object assigned to the given role
43
61
  def assigned_player(role)
44
62
  values(role).first
45
63
  end
64
+
65
+ private
66
+
67
+ def applied
68
+ @applied ||= {}
69
+ end
46
70
  end
47
71
  end
48
72
  end
@@ -10,10 +10,16 @@ module Surrounded
10
10
  const
11
11
  end
12
12
 
13
- # Create attr_reader for the named methods and make them private
13
+ # Create readers for the named methods and make them private. Role names
14
+ # resolve to the current player (original object, or its applied wrapper
15
+ # during a trigger); any other name reads its instance variable.
14
16
  def private_attr_reader(*method_names)
15
- attr_reader(*method_names)
16
- private(*method_names)
17
+ method_names.each do |name|
18
+ define_method(name) do
19
+ role_map.role?(name) ? role_map.current_player(name) : instance_variable_get(:"@#{name}")
20
+ end
21
+ private(name)
22
+ end
17
23
  end
18
24
  end
19
25
  end
@@ -96,7 +96,7 @@ module Surrounded
96
96
  def role?(name, &block)
97
97
  return false unless role_map.role?(name)
98
98
  accessor = block.binding.eval("self")
99
- role_map.role_player?(accessor) && role_map.assigned_player(name)
99
+ role_map.role_player?(accessor) && role_map.current_player(name)
100
100
  end
101
101
 
102
102
  # Check if a given object is a role player in the context.
@@ -153,7 +153,6 @@ module Surrounded
153
153
  end
154
154
 
155
155
  def map_role(role, mod_name, object)
156
- instance_variable_set("@#{role}", object)
157
156
  role_map.update(role, role_module_basename(mod_name), object)
158
157
  end
159
158
 
@@ -168,7 +167,6 @@ module Surrounded
168
167
  end
169
168
 
170
169
  role_player = applicator.call(role_const(behavior), object)
171
- map_role(role, behavior, role_player)
172
170
  end
173
171
  role_player || object
174
172
  end
@@ -206,6 +204,7 @@ module Surrounded
206
204
  def apply_behaviors
207
205
  role_map.each do |role, mod_name, object|
208
206
  player = apply_behavior(role, mod_name, object)
207
+ role_map.apply(role, player)
209
208
  if player.respond_to?(:store_context, true)
210
209
  player.__send__(:store_context) {}
211
210
  end
@@ -213,12 +212,14 @@ module Surrounded
213
212
  end
214
213
 
215
214
  def remove_behaviors
216
- role_map.each do |role, mod_name, player|
215
+ role_map.each do |role, mod_name, object|
216
+ player = role_map.current_player(role)
217
217
  if player.respond_to?(:remove_context, true)
218
218
  player.__send__(:remove_context) {}
219
219
  end
220
220
  remove_behavior(role, mod_name, player)
221
221
  end
222
+ role_map.reset_applied
222
223
  end
223
224
 
224
225
  # List of possible methods to use to add behavior to an object from a module.
@@ -284,8 +285,8 @@ module Surrounded
284
285
  .to_s
285
286
  .split("_")
286
287
  .map { |part|
287
- part.capitalize
288
- }
288
+ part.capitalize
289
+ }
289
290
  .join
290
291
  .sub(/_\d+/, "") + suffix.to_s
291
292
  end
@@ -1,5 +1,5 @@
1
1
  module Surrounded
2
- VERSION = "1.1.1"
2
+ VERSION = "1.1.2"
3
3
 
4
4
  def self.version
5
5
  VERSION
@@ -0,0 +1,39 @@
1
+ require "test_helper"
2
+
3
+ # Refuses value-equality so resolution must use object identity.
4
+ class IdentityResistant
5
+ include Surrounded
6
+
7
+ def initialize(name)
8
+ @name = name
9
+ end
10
+ attr_reader :name
11
+
12
+ def ==(other) = false
13
+ def eql?(other) = false
14
+ def hash = object_id
15
+ end
16
+
17
+ class SpeakerContext
18
+ extend Surrounded::Context
19
+
20
+ initialize :speaker, :listener
21
+
22
+ interface :speaker do
23
+ def greet
24
+ listener.name
25
+ end
26
+ end
27
+
28
+ trigger :speak do
29
+ speaker.greet
30
+ end
31
+ end
32
+
33
+ describe "role identity independent of equality" do
34
+ it "resolves a sibling role by object identity, not ==" do
35
+ a = IdentityResistant.new("A")
36
+ b = IdentityResistant.new("B")
37
+ expect(SpeakerContext.new(speaker: a, listener: b).speak).must_equal "B"
38
+ end
39
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: surrounded
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - "'Jim Gay'"
@@ -80,6 +80,7 @@ files:
80
80
  - test/non_surrounded_role_player_test.rb
81
81
  - test/override_methods_test.rb
82
82
  - test/role_context_method_test.rb
83
+ - test/role_identity_test.rb
83
84
  - test/surrounded_context_test.rb
84
85
  - test/surrounded_test.rb
85
86
  - test/test_helper.rb
@@ -102,7 +103,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
102
103
  - !ruby/object:Gem::Version
103
104
  version: '0'
104
105
  requirements: []
105
- rubygems_version: 3.7.2
106
+ rubygems_version: 4.0.10
106
107
  specification_version: 4
107
108
  summary: Create encapsulated environments for your objects.
108
109
  test_files: []