surrounded 0.9.9 → 0.9.10

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 676d57f548d17fbf0d9112208472938b7c5a2e82
4
- data.tar.gz: 830bf96b8114f786840315b24fba5f90450be813
3
+ metadata.gz: 2236ff5ee05ddc4b3c857e3700ca2205fa966e62
4
+ data.tar.gz: 7e47950aaac3dd0dc4b74f18409ef5590226b5ef
5
5
  SHA512:
6
- metadata.gz: 1bc24e149d0fefd9961ea08c92a01390cbdc24d94a42fa6a06e542a219838572f3c75fa8e65ec6e54324236a53950461e4b81afd7e732a0a6765220e4c5a27d6
7
- data.tar.gz: dd4ee61d04717d8418e011e6421031196bada55ae25d6fb04e24c9913fc06ccc2c199a443c4f554618f3298ef6e7319d81467ee69e43b135cf7da6aa2214f0cd
6
+ metadata.gz: 1b6e97b48a2c4cda950b2507bff57bcb36bad18171774474405d01030f546e43b2df6adb4e8809477517953ee330df5ffde71c8d0e19af1f469103db5eece897
7
+ data.tar.gz: 606cb9ba7d77942a770956eb73581bd4f0fe08caf31bd8305ad404dcea7dd2dbe558521fc908160ba91c2b70bf36c644fc4aef65d940efb6353cf502be24ae3b
@@ -0,0 +1,4 @@
1
+ ---
2
+ notifications:
3
+ pullrequest:
4
+ comment: verbose
@@ -1,17 +1,16 @@
1
+ before_install:
2
+ - gem install bundler
1
3
  language: ruby
2
4
  cache: bundler
3
5
  rvm:
4
- - 2.0.0
5
- - 2.1.5
6
- - 2.2.2
6
+ - 2.3.1
7
+ - 2.2.5
8
+ - 2.1.10
7
9
  - ruby-head
8
10
  - jruby-head
9
- - rbx
10
11
  env:
11
12
  - COVERALLS=true
12
13
  matrix:
13
14
  allow_failures:
14
- - rvm: 2.0.0
15
15
  - rvm: ruby-head
16
16
  - rvm: jruby-head
17
- - rvm: rbx
@@ -0,0 +1,8 @@
1
+ # Change Log
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ ## [0.9.10]
6
+
7
+ - Do something with name collisions when a role player has an existing method of another role in the context.
8
+ - Move InvalidRoleType exception under the host context class namespace. This allows you to rescue from your own namespace.
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013-2015 'Jim Gay'
1
+ Copyright (c) 2013-2016 'Jim Gay'
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -722,6 +722,15 @@ class ActiviatingAccount
722
722
  # ...
723
723
  end
724
724
 
725
+ # Handle method name collisions on role players against role names in the context
726
+ on_name_collision :raise # will raise your context namespaced error: ActiviatingAccount::NameCollisionError
727
+ on_name_collision :warn
728
+ on_name_collision ->(message){ puts "Here's the message! #{message}" }
729
+ on_name_collision :my_custom_handler
730
+ def my_custom_handler(message)
731
+ # do something with the message here
732
+ end
733
+
725
734
  role :activator do # module by default
726
735
  def some_behavior; end
727
736
  end
@@ -910,6 +919,87 @@ end
910
919
 
911
920
  You can remember the method name by the convention that `remove` or `apply` describes it's function, `behavior` refers to the first argument (thet contsant holding the behaviors), and then the name of the role which refers to the role playing object: `remove_behavior_role`.
912
921
 
922
+ ##Name collisions between methods and roles
923
+
924
+ Lets say that you wish to create a context as below, intending to use instances of the following two classes as role players:
925
+
926
+ ```ruby
927
+ class Postcode
928
+ # other methods...
929
+ def code
930
+ @code
931
+ end
932
+
933
+ def country
934
+ @country
935
+ end
936
+ end
937
+
938
+ class Country
939
+ # other methods...
940
+ def country_code
941
+ @code
942
+ end
943
+ end
944
+
945
+ class SendAParcel
946
+ extend Surrounded::Context
947
+
948
+ keyword_initialize :postcode, :country
949
+
950
+ trigger :send do
951
+ postcode.send
952
+ end
953
+
954
+ end
955
+ role :postcode do
956
+ def send
957
+ # do things...
958
+ country_code = country.country_code # name collision...probably raises an exception!
959
+ end
960
+ end
961
+ end
962
+ ```
963
+ 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.
964
+
965
+ To address this issue, use `on_name_collision` to specify the name of a method to use when collisions are found:
966
+
967
+ ```ruby
968
+
969
+ class SendAParcel
970
+ extend Surrounded::Context
971
+
972
+ on_name_collision :raise
973
+ end
974
+
975
+ ```
976
+
977
+ This option will raise an exception (obviously). You may use any method which is available to the context but it must accept a single message as the argument.
978
+
979
+ You can also use a lambda:
980
+
981
+ ```ruby
982
+
983
+ class SendAParcel
984
+ extend Surrounded::Context
985
+
986
+ on_name_collision ->(message){ puts "Here's the message: #{message}"}
987
+ end
988
+
989
+ ```
990
+
991
+ You may also user a class method:
992
+
993
+ ```ruby
994
+ class SendAParcel
995
+ extend Surrounded::Context
996
+
997
+ def self.handle_collisions(message)
998
+ Logger.debug "#{Time.now}: #{message}"
999
+ end
1000
+ end
1001
+ ```
1002
+
913
1003
  ## How to read this code
914
1004
 
915
1005
  If you use this library, it's important to understand it.
@@ -1,4 +1,7 @@
1
1
  module Surrounded
2
+ module Context
3
+ class AccessError < ::StandardError; end
4
+ end
2
5
  module AccessControl
3
6
  def self.extended(base)
4
7
  base.send(:include, AccessMethods)
@@ -7,6 +7,7 @@ require 'surrounded/context/trigger_controls'
7
7
  require 'surrounded/access_control'
8
8
  require 'surrounded/shortcuts'
9
9
  require 'surrounded/east_oriented'
10
+ require 'surrounded/context/name_collision_detector'
10
11
 
11
12
  # Extend your classes with Surrounded::Context to handle their
12
13
  # initialization and application of behaviors to the role players
@@ -18,7 +19,7 @@ module Surrounded
18
19
  module Context
19
20
  def self.extended(base)
20
21
  base.class_eval {
21
- extend RoleBuilders, Initializing, Forwarding
22
+ extend RoleBuilders, Initializing, Forwarding, NameCollisionDetector
22
23
 
23
24
  @triggers = Set.new
24
25
  include InstanceMethods
@@ -28,11 +29,23 @@ module Surrounded
28
29
  include trigger_mod
29
30
 
30
31
  extend TriggerControls
32
+
31
33
  }
34
+ define_exceptions(base)
32
35
  end
33
36
 
34
37
  private
35
38
 
39
+ def self.define_exceptions(klass)
40
+ self.constants.select{|const|
41
+ self.const_get(const) < ::StandardError
42
+ }.map{|exception|
43
+ unless klass.const_defined?(exception)
44
+ klass.const_set(exception, Class.new(self.const_get(exception)))
45
+ end
46
+ }
47
+ end
48
+
36
49
  # Set the default type of implementation for role methods for all contexts.
37
50
  def self.default_role_type
38
51
  @default_role_type ||= :module
@@ -135,6 +148,7 @@ module Surrounded
135
148
  end
136
149
 
137
150
  def map_roles(role_object_array)
151
+ detect_collisions role_object_array
138
152
  role_object_array.to_a.each do |role, object|
139
153
  if self.respond_to?("map_role_#{role}")
140
154
  self.send("map_role_#{role}", object)
@@ -29,8 +29,6 @@ module Surrounded
29
29
 
30
30
  def default_initializer(params, setup_args, &block)
31
31
  private_attr_reader(*setup_args)
32
-
33
- parameters = setup_args.map{|a| "#{a}:"}.join(',')
34
32
  @initializer_block = block || nil
35
33
  mod = Module.new
36
34
  line = __LINE__; mod.class_eval %{
@@ -0,0 +1,70 @@
1
+ module Surrounded
2
+ module Context
3
+ class NameCollisionError <::StandardError; end
4
+ module NameCollisionDetector
5
+
6
+ attr_reader :handler
7
+
8
+ def self.extended(base)
9
+ base.send :include, NameCollisionHandler
10
+ unless defined?(base::NameCollisionError)
11
+ base.const_set(:NameCollinionError, Class.new(::Surrounded::Context::NameCollisionError))
12
+ end
13
+ end
14
+
15
+ def on_name_collision(method_name)
16
+ @handler = method_name
17
+ end
18
+
19
+ module NameCollisionHandler
20
+
21
+ private
22
+
23
+ def detect_collisions(role_object_map)
24
+ if handler
25
+ handle_collisions(collision_warnings(role_object_map))
26
+ end
27
+ end
28
+
29
+ def collision_warnings(role_object_map)
30
+ role_object_map.select{|role, object|
31
+ ![object.methods & role_object_map.keys].flatten.empty?
32
+ }.map{|role, object|
33
+ role_collision_message(role,(object.methods & role_object_map.keys).sort)
34
+ }.join("\n")
35
+ end
36
+
37
+ def handle_collisions(collisions)
38
+ handler_args = [collisions]
39
+ if handler == :raise
40
+ handler_args.unshift self.class::NameCollisionError
41
+ end
42
+
43
+ handler_method.call(*handler_args)
44
+ end
45
+
46
+ def role_collision_message(role, colliding_method_names)
47
+ "#{role} has name collisions with #{colliding_method_names}"
48
+ end
49
+
50
+ def nothing(*); end
51
+
52
+ def handler
53
+ self.class.handler
54
+ end
55
+
56
+ def handler_method
57
+ if handler.respond_to?(:call)
58
+ handler
59
+ elsif respond_to?(handler, true)
60
+ method(handler)
61
+ elsif self.class.respond_to?(handler, true)
62
+ self.class.method(handler)
63
+ else
64
+ method(:nothing)
65
+ end
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -1,5 +1,15 @@
1
1
  module Surrounded
2
2
  module Context
3
+ class InvalidRoleType < ::StandardError
4
+ unless method_defined?(:cause)
5
+ def initialize(msg=nil)
6
+ super
7
+ @cause = $!
8
+ end
9
+ attr_reader :cause
10
+ end
11
+ end
12
+
3
13
  module RoleBuilders
4
14
 
5
15
  # Define behaviors for your role players
@@ -13,7 +23,7 @@ module Surrounded
13
23
  meth.call(name, &block)
14
24
  end
15
25
  rescue NameError => e
16
- raise InvalidRoleType, e.message
26
+ raise self::InvalidRoleType, e.message
17
27
  end
18
28
  alias_method :role_methods, :role
19
29
 
@@ -2,6 +2,8 @@ require 'triad'
2
2
  require 'surrounded/context_errors'
3
3
  module Surrounded
4
4
  module Context
5
+ class InvalidRole < ::Triad::ItemNotPresent; end
6
+
5
7
  class RoleMap
6
8
 
7
9
  class << self
@@ -1,16 +1,5 @@
1
1
  require 'triad'
2
2
  module Surrounded
3
3
  module Context
4
- class InvalidRole < ::Triad::ItemNotPresent; end
5
- class InvalidRoleType < ::StandardError
6
- unless method_defined?(:cause)
7
- def initialize(msg=nil)
8
- super
9
- @cause = $!
10
- end
11
- attr_reader :cause
12
- end
13
- end
14
- class AccessError < ::StandardError; end
15
4
  end
16
- end
5
+ end
@@ -8,6 +8,7 @@ module Surrounded
8
8
  instance.public_send(name)
9
9
  end
10
10
  end
11
+
11
12
  def store_trigger(*names)
12
13
  names.each do |name|
13
14
  define_shortcut(name)
@@ -1,5 +1,5 @@
1
1
  module Surrounded
2
- VERSION = "0.9.9"
2
+ VERSION = "0.9.10"
3
3
 
4
4
  def self.version
5
5
  VERSION
@@ -20,6 +20,6 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_dependency "triad", "~> 0.2.2"
22
22
 
23
- spec.add_development_dependency "bundler", "~> 1.3"
23
+ spec.add_development_dependency "bundler", "~> 1.12"
24
24
  spec.add_development_dependency "rake"
25
25
  end
@@ -13,6 +13,10 @@ class ProxyContext
13
13
  def talking_to_others
14
14
  task.name
15
15
  end
16
+
17
+ def combined_methods
18
+ some_admin_method
19
+ end
16
20
  end
17
21
 
18
22
  wrap :task do
@@ -41,6 +45,10 @@ class ProxyContext
41
45
  trigger :get_admin_method do
42
46
  admin.method(:talking_to_others)
43
47
  end
48
+
49
+ trigger :combined_interface_methods do
50
+ admin.combined_methods
51
+ end
44
52
  end
45
53
 
46
54
  ProxyUser = Class.new do
@@ -83,6 +91,15 @@ describe ProxyContext do
83
91
  assert context.admin_responds?
84
92
  end
85
93
 
94
+ # A Negotiator object merely applies methods to another object
95
+ # so that once the method is called, the object has no knowledge
96
+ # of the module from which the method was applied.
97
+ it 'does not find other interface methods' do
98
+ assert_raises(NameError){
99
+ context.combined_interface_methods
100
+ }
101
+ end
102
+
86
103
  it 'is able to grab methods from the object' do
87
104
  assert_equal :talking_to_others, context.get_admin_method.name
88
105
  end
@@ -0,0 +1,162 @@
1
+ require 'test_helper'
2
+ ##
3
+ # The problem is as follows: When an object already contains a method
4
+ # that has the same name as an actor in the context, then the object
5
+ # defaults to its own method rather than calling the actor.
6
+ # This is simulated in the two classes below, where HasNameCollision has
7
+ # an empty attribute called will_collide (which is always nil). In the following
8
+ # context, when the method collide is called on the actor will_collide, the
9
+ # actor is ignored and the HasNameCollision#will_collide is called instead,
10
+ # returning nil.
11
+ ##
12
+
13
+ class HasNameCollision
14
+ include Surrounded
15
+
16
+ attr_accessor :will_collide # should always return nil
17
+
18
+ def assert_has_role
19
+ true
20
+ end
21
+
22
+ def will_collide=(_); end
23
+ end
24
+
25
+ class ShouldCollide
26
+ include Surrounded
27
+ def collide
28
+ return 'Method called in ShouldCollide'
29
+ end
30
+ end
31
+
32
+ class ContextOverridesName
33
+ extend Surrounded::Context
34
+
35
+ # The specific behaviour we want to test is that when a role has a method that
36
+ # has the same name as another role, then when that method is called strange
37
+ # things happen.
38
+ keyword_initialize :base, :will_collide
39
+ on_name_collision :nothing
40
+
41
+ trigger :check_setup do
42
+ base.assert_has_role
43
+ end
44
+
45
+ trigger :induce_collision do
46
+ base.name_collision
47
+ end
48
+
49
+ role :base do
50
+ def name_collision
51
+ will_collide.collide
52
+ end
53
+
54
+ def assert_has_role
55
+ true
56
+ end
57
+ end
58
+ end
59
+
60
+ class ContextWithMultipleCollisions
61
+ extend Surrounded::Context
62
+
63
+ on_name_collision :warn
64
+
65
+ keyword_initialize :first, :second, :third
66
+ end
67
+
68
+ class First
69
+
70
+ def second;end
71
+ def third;end
72
+ end
73
+
74
+ class Second
75
+ def first;end
76
+
77
+ def third;end
78
+ end
79
+
80
+ class Third
81
+ def first;end
82
+ def second;end
83
+
84
+ end
85
+
86
+ describe 'handling name collisions' do
87
+
88
+ let(:new_context_with_collision){
89
+ ContextOverridesName.new(base: HasNameCollision.new, will_collide: ShouldCollide.new)
90
+ }
91
+
92
+ after do
93
+ ContextOverridesName.instance_eval{
94
+ remove_instance_variable :@handler if defined?(@handler)
95
+ }
96
+ end
97
+
98
+ def set_handler(handler)
99
+ ContextOverridesName.instance_eval{
100
+ on_name_collision handler
101
+ }
102
+ end
103
+
104
+ it 'is works properly without handling collisions' do
105
+ assert new_context_with_collision.check_setup
106
+ end
107
+
108
+ it 'allows a name collision' do
109
+ err = assert_raises(NoMethodError){
110
+ new_context_with_collision.induce_collision
111
+ }
112
+ assert_match(/undefined method \`collide' for nil:NilClass/, err.message)
113
+ end
114
+
115
+ it 'can raise an exception' do
116
+ set_handler :raise
117
+ assert_raises(ContextOverridesName::NameCollisionError){
118
+ new_context_with_collision
119
+ }
120
+ end
121
+
122
+ it 'can print a warning' do
123
+ set_handler :warn
124
+ assert_output(nil, "base has name collisions with [:will_collide]\n") {
125
+ new_context_with_collision
126
+ }
127
+ end
128
+
129
+ let(:create_context_with_multiple_collisions){
130
+ ContextWithMultipleCollisions.new(first: First.new, second: Second.new, third: Third.new)
131
+ }
132
+
133
+ it 'can handle multiple collisions' do
134
+ expected_message = <<ERR
135
+ first has name collisions with [:second, :third]
136
+ second has name collisions with [:first, :third]
137
+ third has name collisions with [:first, :second]
138
+ ERR
139
+ assert_output(nil, expected_message){
140
+ create_context_with_multiple_collisions
141
+ }
142
+ end
143
+
144
+ it 'can use a class method' do
145
+ class ContextOverridesName
146
+ def class_method_handler(message)
147
+ puts message
148
+ end
149
+ end
150
+ set_handler :class_method_handler
151
+ assert_output("base has name collisions with [:will_collide]\n"){
152
+ new_context_with_collision
153
+ }
154
+ end
155
+
156
+ it 'can use a proc' do
157
+ set_handler ->(message){ puts "message from a proc: #{message}"}
158
+ assert_output("message from a proc: base has name collisions with [:will_collide]\n"){
159
+ new_context_with_collision
160
+ }
161
+ end
162
+ end
@@ -89,14 +89,16 @@ describe Surrounded::Context, '.role' do
89
89
 
90
90
  describe 'unknown' do
91
91
  it 'raises an error' do
92
+ class UnknownRole
93
+ extend Surrounded::Context
94
+ end
95
+
92
96
  err = _{
93
- class UnknownRole
94
- extend Surrounded::Context
95
-
97
+ UnknownRole.instance_eval do
96
98
  role :admin, :unknown do
97
99
  end
98
100
  end
99
- }.must_raise Surrounded::Context::InvalidRoleType
101
+ }.must_raise UnknownRole::InvalidRoleType
100
102
  _(err.cause).must_be_kind_of NameError
101
103
  end
102
104
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: surrounded
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.9
4
+ version: 0.9.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - "'Jim Gay'"
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-06-18 00:00:00.000000000 Z
11
+ date: 2016-05-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: triad
@@ -30,14 +30,14 @@ dependencies:
30
30
  requirements:
31
31
  - - "~>"
32
32
  - !ruby/object:Gem::Version
33
- version: '1.3'
33
+ version: '1.12'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
- version: '1.3'
40
+ version: '1.12'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -61,8 +61,10 @@ extra_rdoc_files: []
61
61
  files:
62
62
  - ".codeclimate.yml"
63
63
  - ".gitignore"
64
+ - ".pullreview.yml"
64
65
  - ".simplecov"
65
66
  - ".travis.yml"
67
+ - Changelog.md
66
68
  - Gemfile
67
69
  - LICENSE.txt
68
70
  - README.md
@@ -74,6 +76,7 @@ files:
74
76
  - lib/surrounded/context.rb
75
77
  - lib/surrounded/context/forwarding.rb
76
78
  - lib/surrounded/context/initializing.rb
79
+ - lib/surrounded/context/name_collision_detector.rb
77
80
  - lib/surrounded/context/negotiator.rb
78
81
  - lib/surrounded/context/role_builders.rb
79
82
  - lib/surrounded/context/role_map.rb
@@ -95,6 +98,7 @@ files:
95
98
  - test/example_threaded_test.rb
96
99
  - test/example_wrapper_test.rb
97
100
  - test/initialization_test.rb
101
+ - test/name_collisions_test.rb
98
102
  - test/non_surrounded_role_player_test.rb
99
103
  - test/override_methods_test.rb
100
104
  - test/role_context_method_test.rb
@@ -121,7 +125,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
121
125
  version: '0'
122
126
  requirements: []
123
127
  rubyforge_project:
124
- rubygems_version: 2.4.8
128
+ rubygems_version: 2.5.1
125
129
  signing_key:
126
130
  specification_version: 4
127
131
  summary: Create encapsulated environments for your objects.
@@ -138,6 +142,7 @@ test_files:
138
142
  - test/example_threaded_test.rb
139
143
  - test/example_wrapper_test.rb
140
144
  - test/initialization_test.rb
145
+ - test/name_collisions_test.rb
141
146
  - test/non_surrounded_role_player_test.rb
142
147
  - test/override_methods_test.rb
143
148
  - test/role_context_method_test.rb