surrounded 1.0.0 → 1.1.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 +5 -5
- data/Changelog.md +11 -11
- data/LICENSE.txt +1 -1
- data/README.md +39 -9
- data/Rakefile +12 -5
- data/lib/surrounded/access_control.rb +19 -26
- data/lib/surrounded/context/forwarding.rb +10 -10
- data/lib/surrounded/context/initializing.rb +10 -7
- data/lib/surrounded/context/name_collision_detector.rb +17 -17
- data/lib/surrounded/context/negotiator.rb +13 -13
- data/lib/surrounded/context/role_builders.rb +8 -7
- data/lib/surrounded/context/role_map.rb +20 -13
- data/lib/surrounded/context/seclusion.rb +20 -0
- data/lib/surrounded/context/trigger_controls.rb +14 -16
- data/lib/surrounded/context.rb +64 -72
- data/lib/surrounded/east_oriented.rb +4 -4
- data/lib/surrounded/exceptions.rb +1 -1
- data/lib/surrounded/shortcuts.rb +15 -5
- data/lib/surrounded/version.rb +2 -2
- data/lib/surrounded.rb +7 -7
- data/surrounded.gemspec +21 -16
- data/test/{casting_role_player_test.rb → casting_test_helper.rb} +4 -3
- data/test/collection_role_players_test.rb +16 -16
- data/test/context_access_test.rb +31 -30
- data/test/context_forwarding_test.rb +30 -30
- data/test/context_reuse_test.rb +14 -14
- data/test/context_shortcuts_test.rb +38 -11
- data/test/east_oriented_triggers_test.rb +14 -13
- data/test/example_delegate_class_test.rb +8 -8
- data/test/example_proxy_test.rb +25 -23
- data/test/example_threaded_test.rb +13 -13
- data/test/example_wrapper_test.rb +7 -7
- data/test/initialization_test.rb +25 -26
- data/test/name_collisions_test.rb +48 -42
- data/test/non_surrounded_role_player_test.rb +8 -8
- data/test/override_methods_test.rb +9 -9
- data/test/role_context_method_test.rb +144 -98
- data/test/surrounded_context_test.rb +71 -62
- data/test/surrounded_test.rb +13 -15
- data/test/test_helper.rb +8 -5
- data/test/threaded_context_test.rb +70 -0
- metadata +9 -52
- data/.codeclimate.yml +0 -4
- data/.gitignore +0 -19
- data/.pullreview.yml +0 -4
- data/.simplecov +0 -3
- data/.travis.yml +0 -19
- data/Gemfile +0 -10
- data/examples/bottles.rb +0 -145
- data/examples/rails.rb +0 -57
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
|
-
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e6cd923340dae45ac571dd98abf41be26d111f4569c59d98c27cd4e71f6204d1
|
|
4
|
+
data.tar.gz: a5d11c76eccb01b30fdec8aaced90571449d96c21fe4eced13e55dc1603241da
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 90659aa84b6565797f5b45fb4b637d0dfa21190dfed5366c20b586c4351ca1d0f02fdc5f8d6c45620ac6c9b36dccac28a842ce74b93a2d0a9258297db3d9e0e4
|
|
7
|
+
data.tar.gz: 4831ec1fe6a7dda8a9578776ecb0b2e2030ff37490d15f7e7f35351253bfe7f2c52c9ae1aa8fb7172cb471236b3d5c639f7eae8ef8c5510668a56542ba0fe014
|
data/Changelog.md
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Changelog
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
The format is based on [Keep a Changelog](http://keepachangelog.com/)
|
|
6
|
+
and this project adheres to [Semantic Versioning](http://semver.org/).
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
- Remove code supporting exception cause it InvalidRoleType prior to ruby 2.1
|
|
8
|
+
## [1.1.1] - 2025-10-17
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
### Changed
|
|
11
11
|
|
|
12
|
-
-
|
|
13
|
-
-
|
|
14
|
-
-
|
|
12
|
+
- Version and changelog management with Reissue. (c919a3a)
|
|
13
|
+
- Set the gemspec to use a file list rather than depending on git (69b9a43)
|
|
14
|
+
- Update singularize_name to duplicate a string before altering the value. (3487d0d)
|
|
15
|
+
- Alter the list of identity methods in Negotiator to include __id__. (b98fb5a)
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
### Removed
|
|
17
18
|
|
|
18
|
-
-
|
|
19
|
-
- Move InvalidRoleType exception under the host context class namespace. This allows you to rescue from your own namespace.
|
|
19
|
+
- Code Climate and Pull Review configuration. (46f0057)
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# 
|
|
2
|
+
|
|
2
3
|
## Be in control of business logic.
|
|
3
4
|
|
|
4
|
-
[](https://codeclimate.com/github/saturnflyer/surrounded)
|
|
5
|
+
[](https://github.com/saturnflyer/surrounded/actions)
|
|
6
6
|
|
|
7
7
|
Surrounded is designed to help you better manage your business logic by keeping cohesive behaviors together. Bring objects together to implement your use cases and gain behavior only when necessary.
|
|
8
8
|
|
|
@@ -53,13 +53,13 @@ There are 2 things left to do:
|
|
|
53
53
|
|
|
54
54
|
Initializing contexts does not require the use of keyword arguments, but you may opt out.
|
|
55
55
|
|
|
56
|
-
You should consider using explicit names when
|
|
56
|
+
You should consider using explicit names when initializing now by using `initialize_without_keywords`:
|
|
57
57
|
|
|
58
58
|
```ruby
|
|
59
59
|
class Employment
|
|
60
60
|
extend Surrounded::Context
|
|
61
61
|
|
|
62
|
-
|
|
62
|
+
initialize_without_keywords :employee, :boss
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
user1 = User.find(1)
|
|
@@ -636,6 +636,22 @@ class Organization
|
|
|
636
636
|
end
|
|
637
637
|
```
|
|
638
638
|
|
|
639
|
+
If you want to change the way the singular verson of a role is used, override `singularize_name`:
|
|
640
|
+
|
|
641
|
+
```ruby
|
|
642
|
+
class Organization
|
|
643
|
+
extend Surrounded::Context
|
|
644
|
+
|
|
645
|
+
def singularize_name(name)
|
|
646
|
+
if name == "my special rule"
|
|
647
|
+
# do your thing
|
|
648
|
+
else
|
|
649
|
+
super # use the default
|
|
650
|
+
end
|
|
651
|
+
end
|
|
652
|
+
end
|
|
653
|
+
```
|
|
654
|
+
|
|
639
655
|
## Reusing context objects
|
|
640
656
|
|
|
641
657
|
If you create a context object and need to use the same type of object with new role players, you may use the `rebind` method. It will clear any instance_variables from your context object and map the given objects to their names:
|
|
@@ -824,7 +840,7 @@ context = ActiviatingAccount.new(some_object, some_account)
|
|
|
824
840
|
context.triggers # => lists a Set of triggers
|
|
825
841
|
# when using protect_triggers
|
|
826
842
|
context.triggers # => lists a Set of triggers which may currently be called
|
|
827
|
-
context.
|
|
843
|
+
context.all_triggers # => lists a Set of all triggers (the same as if protect_triggers was _not_ used)
|
|
828
844
|
context.allow?(:trigger_name) # => returns a boolean if the trigger may be run
|
|
829
845
|
|
|
830
846
|
# reuse the context object with new role players
|
|
@@ -835,6 +851,20 @@ context.rebind(activator: another_object, account: another_account)
|
|
|
835
851
|
|
|
836
852
|
The dependencies are minimal. The plan is to keep it that way but allow you to configure things as you need. The [Triad](http://github.com/saturnflyer/triad) project was written specifically to manage the mapping of roles and objects to the modules which contain the behaviors. It is used in Surrounded to keep track of role player, roles, and role constant names but it is not a hard requirement. You may implement your own but presently you'll need to dive into the implementation to fully understand how. Future updates may provide better support and guidance.
|
|
837
853
|
|
|
854
|
+
If you want to override the class used for mapping roles to behaviors, override the `role_map` method.
|
|
855
|
+
|
|
856
|
+
```ruby
|
|
857
|
+
class MyContext
|
|
858
|
+
extend Surrounded::Context
|
|
859
|
+
|
|
860
|
+
def role_map
|
|
861
|
+
@container ||= role_mapper_class.new(base: MySpecialDataContainer)
|
|
862
|
+
end
|
|
863
|
+
end
|
|
864
|
+
```
|
|
865
|
+
|
|
866
|
+
The class you provide will be initialized with `new` and is expected to implement the methods: `:update`, `:each`, `:values`, and `:keys`.
|
|
867
|
+
|
|
838
868
|
If you're using [Casting](http://github.com/saturnflyer/casting), for example, Surrounded will attempt to use that before extending an object, but it will still work without it.
|
|
839
869
|
|
|
840
870
|
## Support for other ways to apply behavior
|
|
@@ -890,7 +920,7 @@ end
|
|
|
890
920
|
|
|
891
921
|
If you'd like to use a special approach for just a single role, you may do that too.
|
|
892
922
|
|
|
893
|
-
When applying behaviors from a role to your role players, your Surrounded context will first look for a method named
|
|
923
|
+
When applying behaviors from a role to your role players, your Surrounded context will first look for a method named `"apply_behavior_#{role}"`. Define your own method and set it to accept 2 arguments: the role constant and the role player.
|
|
894
924
|
|
|
895
925
|
```ruby
|
|
896
926
|
class MyCustomContext
|
|
@@ -918,9 +948,9 @@ class MyCustomContext
|
|
|
918
948
|
end
|
|
919
949
|
```
|
|
920
950
|
|
|
921
|
-
You can remember the method name by the convention that `remove` or `apply` describes it's function, `behavior` refers to the first argument (
|
|
951
|
+
You can remember the method name by the convention that `remove` or `apply` describes it's function, `behavior` refers to the first argument (the constant holding the behaviors), and then the name of the role which refers to the role playing object: `remove_behavior_role`.
|
|
922
952
|
|
|
923
|
-
##Name collisions between methods and roles
|
|
953
|
+
## Name collisions between methods and roles
|
|
924
954
|
|
|
925
955
|
Lets say that you wish to create a context as below, intending to use instances of the following two classes as role players:
|
|
926
956
|
|
|
@@ -952,7 +982,6 @@ Lets say that you wish to create a context as below, intending to use instances
|
|
|
952
982
|
postcode.send
|
|
953
983
|
end
|
|
954
984
|
|
|
955
|
-
end
|
|
956
985
|
role :postcode do
|
|
957
986
|
def send
|
|
958
987
|
# do things...
|
|
@@ -961,6 +990,7 @@ Lets say that you wish to create a context as below, intending to use instances
|
|
|
961
990
|
end
|
|
962
991
|
end
|
|
963
992
|
```
|
|
993
|
+
|
|
964
994
|
When you call the `:send` trigger you are likely to be greeted with an `NoMethodError` exception. The reason for this is that there is a name collision between `Postcode#country`, and the `:country` role in the `SendAParcel` context. Where a name collision exists, the method in the role player overrides that of the calling class and you get unexpected results.
|
|
965
995
|
|
|
966
996
|
To address this issue, use `on_name_collision` to specify the name of a method to use when collisions are found:
|
data/Rakefile
CHANGED
|
@@ -1,17 +1,24 @@
|
|
|
1
1
|
require "bundler/gem_tasks"
|
|
2
2
|
|
|
3
|
-
require
|
|
3
|
+
require "rake/testtask"
|
|
4
4
|
|
|
5
5
|
Rake::TestTask.new do |t|
|
|
6
6
|
t.libs << "test"
|
|
7
|
-
t.test_files = FileList[
|
|
8
|
-
t.ruby_opts = ["-w"]
|
|
7
|
+
t.test_files = FileList["test/*_test.rb"]
|
|
9
8
|
t.verbose = true
|
|
9
|
+
t.warning = true
|
|
10
10
|
end
|
|
11
|
-
task :default => :test
|
|
12
11
|
|
|
12
|
+
task default: :test
|
|
13
|
+
|
|
14
|
+
require "reissue/gem"
|
|
15
|
+
|
|
16
|
+
Reissue::Task.create :reissue do |task|
|
|
17
|
+
task.version_file = "lib/surrounded/version.rb"
|
|
18
|
+
task.fragment = :git
|
|
19
|
+
end
|
|
13
20
|
|
|
14
21
|
# task :mutant, [:class] do |task, args|
|
|
15
22
|
# klass = args[:class] || 'Surrounded'
|
|
16
23
|
# sh "bundle exec mutant --include lib --require surrounded --use minitest #{klass}"
|
|
17
|
-
# end
|
|
24
|
+
# end
|
|
@@ -2,75 +2,68 @@ module Surrounded
|
|
|
2
2
|
module Context
|
|
3
3
|
class AccessError < ::StandardError; end
|
|
4
4
|
end
|
|
5
|
+
|
|
5
6
|
module AccessControl
|
|
6
7
|
def self.extended(base)
|
|
7
8
|
base.send(:include, AccessMethods)
|
|
8
9
|
Surrounded::Exceptions.define(base, exceptions: :AccessError)
|
|
9
10
|
end
|
|
10
|
-
|
|
11
|
+
|
|
11
12
|
private
|
|
12
|
-
|
|
13
|
+
|
|
13
14
|
def disallow(*names, &block)
|
|
14
15
|
names.map do |name|
|
|
15
16
|
define_access_method(name, &block)
|
|
16
17
|
end
|
|
17
18
|
end
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
alias_method :guard, :disallow
|
|
20
|
+
|
|
20
21
|
def trigger_return_content(name, *args, &block)
|
|
21
22
|
%{
|
|
22
23
|
|
|
23
24
|
method_restrictor = "disallow_#{name}?"
|
|
24
25
|
if self.respond_to?(method_restrictor, true) && self.send(method_restrictor)
|
|
25
|
-
raise ::#{self
|
|
26
|
+
raise ::#{self}::AccessError.new("access to #{self.name}##{name} is not allowed")
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
#{super}
|
|
29
30
|
}
|
|
30
31
|
end
|
|
31
|
-
|
|
32
|
+
|
|
32
33
|
def define_access_method(name, &block)
|
|
33
34
|
mod = Module.new
|
|
34
35
|
mod.class_eval {
|
|
35
36
|
define_method "disallow_#{name}?" do
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
remove_behaviors
|
|
41
|
-
end
|
|
37
|
+
apply_behaviors
|
|
38
|
+
instance_exec(&block)
|
|
39
|
+
ensure
|
|
40
|
+
remove_behaviors
|
|
42
41
|
end
|
|
43
42
|
}
|
|
44
43
|
const_set("SurroundedAccess#{name}", mod)
|
|
45
44
|
include mod
|
|
46
45
|
end
|
|
47
|
-
|
|
46
|
+
|
|
48
47
|
module AccessMethods
|
|
49
48
|
# Return a Set of all defined triggers regardless of any disallow blocks
|
|
50
49
|
def all_triggers
|
|
51
50
|
self.class.triggers
|
|
52
51
|
end
|
|
53
|
-
|
|
52
|
+
|
|
54
53
|
# Return a Set of triggers which may be run according to any restrictions defined
|
|
55
54
|
# in disallow blocks.
|
|
56
55
|
def triggers
|
|
57
|
-
all_triggers.select {|name|
|
|
58
|
-
|
|
59
|
-
!self.respond_to?(method_restrictor, true) || !self.send(method_restrictor)
|
|
56
|
+
all_triggers.select { |name|
|
|
57
|
+
allow?(name)
|
|
60
58
|
}.to_set
|
|
61
59
|
end
|
|
62
60
|
|
|
63
61
|
# Ask if the context will allow access to a trigger given the current players.
|
|
64
62
|
def allow?(name)
|
|
65
|
-
unless
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if self.respond_to?("disallow_#{name}?")
|
|
69
|
-
!self.public_send("disallow_#{name}?")
|
|
70
|
-
else
|
|
71
|
-
true
|
|
72
|
-
end
|
|
63
|
+
raise NoMethodError, %(undefined method `#{name}' for #{inspect}) unless respond_to?(name)
|
|
64
|
+
predicate = "disallow_#{name}?"
|
|
65
|
+
!respond_to?(predicate) || !public_send(predicate)
|
|
73
66
|
end
|
|
74
67
|
end
|
|
75
68
|
end
|
|
76
|
-
end
|
|
69
|
+
end
|
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
module Surrounded
|
|
2
2
|
module Context
|
|
3
3
|
module Forwarding
|
|
4
|
-
def forward_trigger(receiver, message, alternate=message)
|
|
5
|
-
raise(ArgumentError, %
|
|
6
|
-
trigger alternate do |*args, &block|
|
|
7
|
-
|
|
4
|
+
def forward_trigger(receiver, message, alternate = message)
|
|
5
|
+
raise(ArgumentError, %(you may not forward '%{m}`) % {m: message}) if ["__id__", "__send__"].include?(message.to_s)
|
|
6
|
+
trigger alternate do |*args, **kwargs, &block|
|
|
7
|
+
send(receiver).public_send(message, *args, **kwargs, &block)
|
|
8
8
|
end
|
|
9
9
|
end
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
def forward_triggers(receiver, *messages)
|
|
12
12
|
messages.each do |message|
|
|
13
13
|
forward_trigger(receiver, message)
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
|
-
|
|
16
|
+
|
|
17
17
|
def forwarding(hash)
|
|
18
18
|
hash.each { |messages, receiver|
|
|
19
19
|
forward_triggers(receiver, *messages)
|
|
20
20
|
}
|
|
21
21
|
end
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
|
|
23
|
+
alias_method :forward, :forward_trigger
|
|
24
|
+
alias_method :forwards, :forward_triggers
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
|
-
end
|
|
27
|
+
end
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
module Surrounded
|
|
2
2
|
module Context
|
|
3
3
|
module Initializing
|
|
4
|
+
extend Seclusion
|
|
5
|
+
|
|
4
6
|
# Shorthand for creating an instance level initialize method which
|
|
5
7
|
# handles the mapping of the given arguments to their named role.
|
|
6
8
|
def initialize_without_keywords(*setup_args, &block)
|
|
7
|
-
parameters = setup_args.join(
|
|
9
|
+
parameters = setup_args.join(",")
|
|
8
10
|
default_initializer(parameters, setup_args, &block)
|
|
9
11
|
end
|
|
10
12
|
|
|
11
13
|
def initialize(*setup_args, &block)
|
|
12
|
-
parameters = setup_args.map{|a| "#{a}:"}.join(
|
|
14
|
+
parameters = setup_args.map { |a| "#{a}:" }.join(",")
|
|
13
15
|
default_initializer(parameters, setup_args, &block)
|
|
14
16
|
end
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
alias_method :keyword_initialize, :initialize
|
|
18
|
+
alias_method :initialize_with_keywords, :keyword_initialize
|
|
17
19
|
|
|
18
20
|
def initializer_block
|
|
19
21
|
@initializer_block
|
|
@@ -30,15 +32,16 @@ module Surrounded
|
|
|
30
32
|
line = __LINE__; mod.class_eval %{
|
|
31
33
|
def initialize(#{params})
|
|
32
34
|
@role_map = role_mapper_class.new
|
|
33
|
-
@initializer_arguments = Hash[#{setup_args
|
|
35
|
+
@initializer_arguments = Hash[#{setup_args}.zip([#{setup_args.join(",")}])]
|
|
34
36
|
map_roles(@initializer_arguments)
|
|
35
37
|
self.class.apply_initializer_block(self)
|
|
36
38
|
end
|
|
37
39
|
}, __FILE__, line
|
|
38
|
-
const_set(
|
|
40
|
+
const_set(:ContextInitializer, mod)
|
|
39
41
|
include mod
|
|
42
|
+
|
|
40
43
|
private_attr_reader :initializer_arguments
|
|
41
44
|
end
|
|
42
45
|
end
|
|
43
46
|
end
|
|
44
|
-
end
|
|
47
|
+
end
|
|
@@ -1,56 +1,56 @@
|
|
|
1
1
|
module Surrounded
|
|
2
2
|
module Context
|
|
3
|
-
class NameCollisionError
|
|
4
|
-
module NameCollisionDetector
|
|
3
|
+
class NameCollisionError < ::StandardError; end
|
|
5
4
|
|
|
5
|
+
module NameCollisionDetector
|
|
6
6
|
attr_reader :handler
|
|
7
7
|
|
|
8
8
|
def self.extended(base)
|
|
9
9
|
base.send :include, NameCollisionHandler
|
|
10
10
|
Surrounded::Exceptions.define(base, exceptions: :NameCollisionError)
|
|
11
11
|
end
|
|
12
|
-
|
|
12
|
+
|
|
13
13
|
def on_name_collision(method_name)
|
|
14
14
|
@handler = method_name
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
module NameCollisionHandler
|
|
18
|
-
|
|
19
18
|
private
|
|
20
|
-
|
|
19
|
+
|
|
21
20
|
def detect_collisions(role_object_map)
|
|
22
21
|
if handler
|
|
23
22
|
handle_collisions(collision_warnings(role_object_map))
|
|
24
23
|
end
|
|
25
24
|
end
|
|
26
|
-
|
|
25
|
+
|
|
27
26
|
def collision_warnings(role_object_map)
|
|
28
|
-
role_object_map.select{|role, object|
|
|
27
|
+
role_object_map.select { |role, object|
|
|
29
28
|
![object.methods & role_object_map.keys].flatten.empty?
|
|
30
|
-
}.map{|role, object|
|
|
31
|
-
role_collision_message(role,(object.methods & role_object_map.keys).sort)
|
|
29
|
+
}.map { |role, object|
|
|
30
|
+
role_collision_message(role, (object.methods & role_object_map.keys).sort)
|
|
32
31
|
}.join("\n")
|
|
33
32
|
end
|
|
34
|
-
|
|
33
|
+
|
|
35
34
|
def handle_collisions(collisions)
|
|
36
35
|
handler_args = [collisions]
|
|
37
36
|
if handler == :raise
|
|
38
37
|
handler_args.unshift self.class::NameCollisionError
|
|
39
38
|
end
|
|
40
|
-
|
|
39
|
+
|
|
41
40
|
handler_method.call(*handler_args)
|
|
42
41
|
end
|
|
43
|
-
|
|
42
|
+
|
|
44
43
|
def role_collision_message(role, colliding_method_names)
|
|
45
44
|
"#{role} has name collisions with #{colliding_method_names}"
|
|
46
45
|
end
|
|
47
|
-
|
|
48
|
-
def nothing(*)
|
|
46
|
+
|
|
47
|
+
def nothing(*)
|
|
48
|
+
end
|
|
49
49
|
|
|
50
50
|
def handler
|
|
51
51
|
self.class.handler
|
|
52
52
|
end
|
|
53
|
-
|
|
53
|
+
|
|
54
54
|
def handler_method
|
|
55
55
|
if handler.respond_to?(:call)
|
|
56
56
|
handler
|
|
@@ -59,10 +59,10 @@ module Surrounded
|
|
|
59
59
|
elsif self.class.respond_to?(handler, true)
|
|
60
60
|
self.class.method(handler)
|
|
61
61
|
else
|
|
62
|
-
raise ArgumentError, %
|
|
62
|
+
raise ArgumentError, %(your name collision handler was set to `#{handler}' but there is no instance nor class method of that name)
|
|
63
63
|
end
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
|
-
end
|
|
68
|
+
end
|
|
@@ -14,24 +14,24 @@ module Surrounded
|
|
|
14
14
|
# For each method in the module, directly forward to the wrapped object to
|
|
15
15
|
# circumvent method_missing
|
|
16
16
|
mod.instance_methods(false).each do |meth|
|
|
17
|
-
|
|
18
|
-
def #{meth}(
|
|
19
|
-
__behaviors__.instance_method(:#{meth}).bind(@object)
|
|
17
|
+
klass.class_eval <<~MOD, __FILE__, __LINE__ + 1
|
|
18
|
+
def #{meth}(...)
|
|
19
|
+
@#{meth}_method ||= __behaviors__.instance_method(:#{meth}).bind(@object)
|
|
20
|
+
@#{meth}_method.call(...)
|
|
20
21
|
end
|
|
21
|
-
|
|
22
|
+
MOD
|
|
22
23
|
end
|
|
23
24
|
klass
|
|
24
25
|
end
|
|
25
26
|
end
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
identity = %w[__send__ object_id equal?]
|
|
28
|
+
identity = %w[__send__ __id__ object_id equal?]
|
|
29
29
|
method_access = %w[respond_to? method __behaviors__]
|
|
30
30
|
|
|
31
|
-
reserved_methods = (identity + method_access).join(
|
|
31
|
+
reserved_methods = (identity + method_access).join("|")
|
|
32
32
|
|
|
33
33
|
# Remove all methods except the reserved methods
|
|
34
|
-
instance_methods.reject{ |m|
|
|
34
|
+
instance_methods.reject { |m|
|
|
35
35
|
m.to_s =~ /#{reserved_methods}/
|
|
36
36
|
}.each do |meth|
|
|
37
37
|
undef_method meth
|
|
@@ -51,19 +51,19 @@ module Surrounded
|
|
|
51
51
|
self
|
|
52
52
|
end
|
|
53
53
|
# These only differ in the message they send
|
|
54
|
-
|
|
54
|
+
alias_method :remove_context, :store_context
|
|
55
55
|
|
|
56
56
|
def initialize(object)
|
|
57
57
|
@object = object
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
-
def method_missing(meth,
|
|
61
|
-
@object.send(meth,
|
|
60
|
+
def method_missing(meth, ...)
|
|
61
|
+
@object.send(meth, ...)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
|
-
def respond_to_missing?(meth, include_private=false)
|
|
64
|
+
def respond_to_missing?(meth, include_private = false)
|
|
65
65
|
@object.respond_to?(meth, include_private)
|
|
66
66
|
end
|
|
67
67
|
end
|
|
68
68
|
end
|
|
69
|
-
end
|
|
69
|
+
end
|
|
@@ -3,13 +3,14 @@ module Surrounded
|
|
|
3
3
|
class InvalidRoleType < ::StandardError; end
|
|
4
4
|
|
|
5
5
|
module RoleBuilders
|
|
6
|
+
extend Seclusion
|
|
6
7
|
|
|
7
8
|
def self.extended(base)
|
|
8
9
|
Surrounded::Exceptions.define(base, exceptions: :InvalidRoleType)
|
|
9
10
|
end
|
|
10
11
|
|
|
11
12
|
# Define behaviors for your role players
|
|
12
|
-
def role(name, type=default_role_type, &block)
|
|
13
|
+
def role(name, type = default_role_type, &block)
|
|
13
14
|
if type == :module
|
|
14
15
|
mod_name = RoleName(name)
|
|
15
16
|
mod = Module.new(&block).send(:include, ::Surrounded)
|
|
@@ -25,7 +26,7 @@ module Surrounded
|
|
|
25
26
|
|
|
26
27
|
# Create a named behavior for a role using the standard library SimpleDelegator.
|
|
27
28
|
def wrap(name, &block)
|
|
28
|
-
require
|
|
29
|
+
require "delegate"
|
|
29
30
|
wrapper_name = RoleName(name)
|
|
30
31
|
klass = private_const_set(wrapper_name, Class.new(SimpleDelegator, &block))
|
|
31
32
|
klass.send(:include, Surrounded)
|
|
@@ -35,7 +36,7 @@ module Surrounded
|
|
|
35
36
|
# Create a named behavior for a role using the standard library DelegateClass.
|
|
36
37
|
# This ties the implementation of the role to a specific class or module API.
|
|
37
38
|
def delegate_class(name, class_name, &block)
|
|
38
|
-
require
|
|
39
|
+
require "delegate"
|
|
39
40
|
wrapper_name = RoleName(name)
|
|
40
41
|
klass = private_const_set(wrapper_name, DelegateClass(Object.const_get(class_name.to_s)))
|
|
41
42
|
klass.class_eval(&block)
|
|
@@ -52,19 +53,19 @@ module Surrounded
|
|
|
52
53
|
# access them from any of its methods.
|
|
53
54
|
def interface(name, &block)
|
|
54
55
|
# AdminInterface
|
|
55
|
-
interface_name = RoleName(name,
|
|
56
|
+
interface_name = RoleName(name, "Interface")
|
|
56
57
|
behavior = private_const_set(interface_name, Module.new(&block))
|
|
57
58
|
|
|
58
|
-
require
|
|
59
|
+
require "surrounded/context/negotiator"
|
|
59
60
|
# Admin
|
|
60
61
|
private_const_set(RoleName(name), Negotiator.for_role(behavior))
|
|
61
62
|
end
|
|
62
63
|
|
|
63
64
|
private
|
|
64
|
-
|
|
65
|
+
|
|
66
|
+
def RoleName(text, suffix = nil)
|
|
65
67
|
RoleName.new(text, suffix)
|
|
66
68
|
end
|
|
67
|
-
|
|
68
69
|
end
|
|
69
70
|
end
|
|
70
71
|
end
|
|
@@ -1,41 +1,48 @@
|
|
|
1
|
-
require
|
|
2
|
-
require
|
|
1
|
+
require "triad"
|
|
2
|
+
require "forwardable"
|
|
3
3
|
module Surrounded
|
|
4
4
|
module Context
|
|
5
5
|
class RoleMap
|
|
6
6
|
extend Forwardable
|
|
7
7
|
|
|
8
8
|
class << self
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
9
|
+
# Get the role map container and provide an alternative if desired
|
|
10
|
+
# Ex: RoleMap.from_base(SomeCustomContainer)
|
|
11
|
+
def from_base(klass = ::Triad)
|
|
12
|
+
unless const_defined?(:Container)
|
|
13
|
+
role_mapper = Class.new(self)
|
|
14
|
+
role_mapper.container_class = (klass)
|
|
15
|
+
Surrounded::Exceptions.define(role_mapper, exceptions: :ItemNotPresent, namespace: klass)
|
|
16
|
+
const_set(:Container, role_mapper)
|
|
17
|
+
end
|
|
18
|
+
const_get(:Container)
|
|
15
19
|
end
|
|
16
20
|
|
|
17
|
-
|
|
18
|
-
@container_class = klass
|
|
19
|
-
end
|
|
21
|
+
attr_writer :container_class
|
|
20
22
|
end
|
|
21
23
|
|
|
24
|
+
def_delegators :container, :update, :each, :values, :keys
|
|
25
|
+
|
|
22
26
|
def container
|
|
23
27
|
@container ||= self.class.instance_variable_get(:@container_class).new
|
|
24
28
|
end
|
|
25
29
|
|
|
30
|
+
# Check if a role exists in the map
|
|
26
31
|
def role?(role)
|
|
27
32
|
keys.include?(role)
|
|
28
33
|
end
|
|
29
34
|
|
|
35
|
+
# Check if an object is playing a role in this map
|
|
30
36
|
def role_player?(object)
|
|
31
37
|
!values(object).empty?
|
|
32
|
-
rescue ::
|
|
38
|
+
rescue container.class::ItemNotPresent
|
|
33
39
|
false
|
|
34
40
|
end
|
|
35
41
|
|
|
42
|
+
# Get the object playing the given role
|
|
36
43
|
def assigned_player(role)
|
|
37
44
|
values(role).first
|
|
38
45
|
end
|
|
39
46
|
end
|
|
40
47
|
end
|
|
41
|
-
end
|
|
48
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Surrounded
|
|
2
|
+
module Context
|
|
3
|
+
module Seclusion
|
|
4
|
+
# Set a named constant and make it private
|
|
5
|
+
def private_const_set(name, const)
|
|
6
|
+
unless role_const_defined?(name)
|
|
7
|
+
const = const_set(name, const)
|
|
8
|
+
private_constant name.to_sym
|
|
9
|
+
end
|
|
10
|
+
const
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Create attr_reader for the named methods and make them private
|
|
14
|
+
def private_attr_reader(*method_names)
|
|
15
|
+
attr_reader(*method_names)
|
|
16
|
+
private(*method_names)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|