surrounded 0.3.0 → 0.3.1

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: 3e6e141d7aa5fdf78d69d250369fa15f2f6b5f6d
4
- data.tar.gz: 5251f5c762065460afa6da69f0da2bf2f68529dc
3
+ metadata.gz: 06fdae60ee9aa6572db5ba8151e49bfba7257501
4
+ data.tar.gz: dbed795a64a2ed83a405725e4a0bbf826952cc32
5
5
  SHA512:
6
- metadata.gz: 7188a2fadebd4d332f402c8fba1cabb77968623a698a02a083ef59266a1ee8b4223216f5aa70d9c5886ddc8af1fef9058686a67ce512ab94e4571cc42e23da39
7
- data.tar.gz: 0ca5c9b9ead86e6d87d0209c5fa0e59cb4b67ac957c0d0425f85704270f1879702cade79de3af98323df85ff1dc9fe2a3a22a7a19e789adf58bcdda534ea89d9
6
+ metadata.gz: b930aaf822c3a9b77c8fb9390e8dd32d64077b3ede2f6cd8f347f429b07ad8b60c899585cf1453a0c7359d3eab48af0b73c64f19a33a10213d5ce6cb2b27fcfe
7
+ data.tar.gz: 00d58b00643d05d17f4ece1f7b1092104f4747535fe67368441fcfe57f0a52d2ebc31a4fba39569601d7d99d37a321cedc8f7be3fe713d88ee7d687d71b0d5ff
data/examples/rails.rb CHANGED
@@ -34,6 +34,10 @@ end
34
34
 
35
35
  class SomethingController < ApplicationController
36
36
  def create
37
- SomeUseCase.new(current_user, User.last, self).do_something
37
+ surround(current_user, User.last).do_something
38
+ end
39
+
40
+ def surround(admin, other)
41
+ SomeUseCase.new(admin, other, self)
38
42
  end
39
43
  end
@@ -0,0 +1,209 @@
1
+ # Surrounded aims to make things simple and get out of your way.
2
+
3
+ Most of what you care about is defining the behavior of objects. How they interact is important.
4
+ The purpose of this library is to clear away the details of getting things setup.
5
+
6
+ You should read the [main README](../../README.md) to get the gist of what's going on, if you haven't.
7
+
8
+ When you get started, you'll probably be specifying things exactly how you want them. But you can easily make changes.
9
+
10
+ ```ruby
11
+ class MoneyTransfer
12
+ extend Surrounded::Context
13
+
14
+ def initialize(source, destination, amount)
15
+ @source = source.extend(Source)
16
+ @destination = destination
17
+ @amount = amount
18
+ end
19
+
20
+ attr_reader :source, :destination, :amount
21
+ private :source, :destination, :amount
22
+
23
+ def execute
24
+ source.transfer
25
+ end
26
+
27
+ module Source
28
+ def transfer
29
+ self.balance -= amount
30
+ destination.balance += amount
31
+ self
32
+ end
33
+ end
34
+ end
35
+ ```
36
+
37
+ That's a lot of setup just to create the `execute` and `transfer` methods.
38
+
39
+ Here's a shortened version:
40
+
41
+ ```ruby
42
+ class MoneyTransfer
43
+ extend Surrounded::Context
44
+
45
+ initialize(:source, :destination, :amount)
46
+
47
+ trigger :execute do
48
+ source.transfer
49
+ end
50
+
51
+ module Source
52
+ def transfer
53
+ self.balance -= amount
54
+ destination.balance += amount
55
+ self
56
+ end
57
+ end
58
+ end
59
+ ```
60
+
61
+ Now it's cleaned up a bit and is much easier to see what's important.
62
+ If you decide you want to try out wrappers, you can start making changes to use `SimpleDelegator` from the standard library, but you'll have to remember to 1) `include Surrounded` 2) to do the initialize yourself again and 3) return the correct object from your role method.
63
+
64
+ Simply by changing your mind to use `SimpleDelegator`, here's what you'll need to do:
65
+
66
+ ```ruby
67
+ class MoneyTransfer
68
+ extend Surrounded::Context
69
+
70
+ def initialize(source, destination, amount)
71
+ @source = Source.new(source)
72
+ @destination = destination
73
+ @amount = amount
74
+ end
75
+
76
+ attr_reader :source, :destination, :amount
77
+ private :source, :destination, :amount
78
+
79
+ def execute
80
+ source.transfer
81
+ end
82
+
83
+ class Source < SimpleDelegator
84
+ include Surrounded
85
+
86
+ def transfer
87
+ self.balance -= amount
88
+ destination.balance += amount
89
+ __getobj__
90
+ end
91
+ end
92
+ end
93
+ ```
94
+ Once again, it's big and ugly. But surrounded has your back; you can istead do this:
95
+
96
+ ```ruby
97
+ class MoneyTransfer
98
+ extend Surrounded::Context
99
+
100
+ initialize(:source, :destination, :amount)
101
+
102
+ trigger :execute do
103
+ source.transfer
104
+ end
105
+
106
+ wrap :source do
107
+ def transfer
108
+ self.balance -= amount
109
+ destination.balance += amount
110
+ __getobj__
111
+ end
112
+ end
113
+ end
114
+ ```
115
+
116
+ That's not much different from the module and it takes care of using `SimpleDelegator` and including `Surrounded` for you. If you want to make changing your approach even easier, you can use the `role` method instead.
117
+
118
+ ```ruby
119
+ class MoneyTransfer
120
+ extend Surrounded::Context
121
+
122
+ initialize(:source, :destination, :amount)
123
+
124
+ trigger :execute do
125
+ source.transfer
126
+ end
127
+
128
+ role :source, :wrap do
129
+ def transfer
130
+ self.balance -= amount
131
+ destination.balance += amount
132
+ __getobj__
133
+ end
134
+ end
135
+ end
136
+ ```
137
+
138
+ This way you can swap between implementations:
139
+
140
+ ```ruby
141
+
142
+ # this uses modules
143
+ role :source do
144
+ def transfer
145
+ self.balance -= amount
146
+ destination.balance += amount
147
+ self
148
+ end
149
+ end
150
+
151
+ # this uses SimpleDelegator and Surrounded
152
+ role :source do
153
+ def transfer
154
+ self.balance -= amount
155
+ destination.balance += amount
156
+ __getobj__
157
+ end
158
+ end
159
+
160
+ # this uses a special interface object which pulls
161
+ # methods from a module and applies them to your object.
162
+ role :source, :interface do
163
+ def transfer
164
+ self.balance -= amount
165
+ destination.balance += amount
166
+ self
167
+ end
168
+ end
169
+ ```
170
+
171
+ The `:interface` option is a special object which has all of its methods removed (excepting `__send__` and `object_id`) so that other methods will be pulled from the ones that you define, or from the object it attempts to proxy.
172
+
173
+ Notice that the `:interface` allows you to return `self` whereas the `:wrap` acts more like a wrapper and forces you to deal with that shortcoming by using it's wrapped-object-accessor method: `__getobj__`.
174
+
175
+ If you'd like to choose one and use it all the time, you can set the default:
176
+
177
+ ```ruby
178
+ class MoneyTransfer
179
+ extend Surrounded::Context
180
+
181
+ self.default_role_type = :interface # also :wrap, :wrapper, or :module
182
+
183
+ role :source do
184
+ def transfer
185
+ self.balance -= amount
186
+ destination.balance += amount
187
+ self
188
+ end
189
+ end
190
+ end
191
+ ```
192
+
193
+ Or, if you like, you can choose the default for your entire project:
194
+
195
+ ```ruby
196
+ Surrounded::Context.default_role_type = :interface
197
+
198
+ class MoneyTransfer
199
+ extend Surrounded::Context
200
+
201
+ role :source do
202
+ def transfer
203
+ self.balance -= amount
204
+ destination.balance += amount
205
+ self
206
+ end
207
+ end
208
+ end
209
+ ```
@@ -9,6 +9,14 @@ module Surrounded
9
9
  base.singleton_class.send(:alias_method, :setup, :initialize)
10
10
  end
11
11
 
12
+ def self.default_role_type
13
+ @default_role_type ||= :module
14
+ end
15
+
16
+ def self.default_role_type=(type)
17
+ @default_role_type = type
18
+ end
19
+
12
20
  def new(*)
13
21
  instance = super
14
22
  instance.instance_variable_set('@__apply_role_policy', __apply_role_policy)
@@ -21,10 +29,37 @@ module Surrounded
21
29
 
22
30
  private
23
31
 
32
+ def private_const_set(name, const)
33
+ const = const_set(name, const)
34
+ private_constant name
35
+ const
36
+ end
37
+
38
+ def default_role_type
39
+ @default_role_type ||= Surrounded::Context.default_role_type
40
+ end
41
+
42
+ def default_role_type=(type)
43
+ @default_role_type = type
44
+ end
45
+
46
+ def role(name, type=nil, &block)
47
+ role_type = type || default_role_type
48
+ case role_type
49
+ when :wrap, :wrapper then wrap(name, &block)
50
+ when :interface then interface(name, &block)
51
+ when :module then
52
+ mod_name = name.to_s.gsub(/(?:^|_)([a-z])/){ $1.upcase }
53
+ private_const_set(mod_name, Module.new(&block))
54
+ else
55
+ raise InvalidRoleType.new
56
+ end
57
+ end
58
+
24
59
  def wrap(name, &block)
25
60
  require 'delegate'
26
61
  wrapper_name = name.to_s.gsub(/(?:^|_)([a-z])/){ $1.upcase }
27
- klass = const_set(wrapper_name, Class.new(SimpleDelegator, &block))
62
+ klass = private_const_set(wrapper_name, Class.new(SimpleDelegator, &block))
28
63
  klass.send(:include, Surrounded)
29
64
  end
30
65
 
@@ -33,7 +68,7 @@ module Surrounded
33
68
  class_basename = name.to_s.gsub(/(?:^|_)([a-z])/){ $1.upcase }
34
69
  interface_name = class_basename + 'Interface'
35
70
 
36
- behavior = const_set(interface_name, Module.new(&block))
71
+ behavior = private_const_set(interface_name, Module.new(&block))
37
72
 
38
73
  require 'surrounded/context/negotiator'
39
74
  define_method(name) do
@@ -92,6 +127,12 @@ module Surrounded
92
127
  @triggers << name
93
128
  end
94
129
 
130
+ def role_const(name)
131
+ if const_defined?(name)
132
+ const_get(name)
133
+ end
134
+ end
135
+
95
136
  module InstanceMethods
96
137
  def role?(name, &block)
97
138
  return false unless role_map.role?(name)
@@ -128,7 +169,7 @@ module Surrounded
128
169
  applicator = behavior.is_a?(Class) ? method(:add_class_interface) : method(:add_module_interface)
129
170
 
130
171
  role_player = applicator.call(object, behavior)
131
- map_role(role, role_module_basename(behavior), role_player)
172
+ map_role(role, role_module_basename(behavior), role_player) if behavior
132
173
  role_player.store_context(self)
133
174
  role_player
134
175
  end
@@ -155,7 +196,7 @@ module Surrounded
155
196
  object.remove_context
156
197
  role_player = object.method(remover_name).call
157
198
 
158
- map_role(role, role_module_basename(behavior), role_player)
199
+ map_role(role, role_module_basename(behavior), role_player) if behavior
159
200
 
160
201
  role_player
161
202
  end
@@ -170,8 +211,8 @@ module Surrounded
170
211
 
171
212
  def traverse_map(applicator)
172
213
  role_map.each do |role, mod_name, object|
173
- if self.class.const_defined?(mod_name)
174
- applicator.call(role, self.class.const_get(mod_name), object)
214
+ if role_const_defined?(mod_name)
215
+ applicator.call(role, role_const(mod_name), object)
175
216
  end
176
217
  end
177
218
  end
@@ -199,6 +240,14 @@ module Surrounded
199
240
  def role_module_basename(mod)
200
241
  mod.to_s.split('::').last
201
242
  end
243
+
244
+ def role_const(name)
245
+ self.class.send(:role_const, name)
246
+ end
247
+
248
+ def role_const_defined?(name)
249
+ self.class.const_defined?(name)
250
+ end
202
251
  end
203
252
  end
204
253
  end
@@ -11,13 +11,13 @@ module Surrounded
11
11
 
12
12
  private
13
13
 
14
- def initialize(object, behavior)
15
- @object, @behavior = object, behavior
14
+ def initialize(object, behaviors)
15
+ @object, @behaviors = object, behaviors
16
16
  end
17
17
 
18
18
  def method_missing(meth, *args, &block)
19
- if @behavior.instance_methods.include?(meth)
20
- the_method = @behavior.instance_method(meth)
19
+ if @behaviors.instance_methods.include?(meth)
20
+ the_method = @behaviors.instance_method(meth)
21
21
  the_method.bind(@object).call(*args, &block)
22
22
  else
23
23
  @object.send(meth, *args, &block)
@@ -2,5 +2,6 @@ require 'triad'
2
2
  module Surrounded
3
3
  module Context
4
4
  class InvalidRole < ::Triad::KeyNotPresent; end
5
+ class InvalidRoleType < StandardError; end
5
6
  end
6
7
  end
@@ -1,3 +1,3 @@
1
1
  module Surrounded
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -0,0 +1,143 @@
1
+ require 'test_helper'
2
+
3
+
4
+ describe Surrounded::Context, '.role' do
5
+ class RoleContextTester
6
+ extend Surrounded::Context
7
+
8
+ role :admin do
9
+ end
10
+ end
11
+
12
+ describe 'modules' do
13
+ it 'creates a module' do
14
+ role = RoleContextTester.const_get('Admin')
15
+ refute_instance_of Class, role
16
+ assert_kind_of Module, role
17
+ end
18
+
19
+ it 'marks the constant private' do
20
+ error = assert_raises(NameError){
21
+ RoleContextTester::Admin
22
+ }
23
+ assert_match /private constant/, error.message
24
+ end
25
+ end
26
+
27
+ class WrapperRoleContext
28
+ extend Surrounded::Context
29
+
30
+ role :admin, :wrap do
31
+
32
+ end
33
+ end
34
+
35
+ describe 'wrappers' do
36
+ it 'creates a wrapper' do
37
+ role = WrapperRoleContext.const_get('Admin')
38
+ assert_includes(role.ancestors, SimpleDelegator)
39
+ end
40
+
41
+ it 'marks the constant private' do
42
+ error = assert_raises(NameError){
43
+ WrapperRoleContext::Admin
44
+ }
45
+ assert_match /private constant/, error.message
46
+ end
47
+ end
48
+ if RedCard.check '2.0'
49
+ class InterfaceContext
50
+ extend Surrounded::Context
51
+
52
+ initialize(:admin, :other)
53
+
54
+ role :admin, :interface do
55
+ def hello
56
+ 'hello from admin'
57
+ end
58
+ end
59
+
60
+ trigger :admin_hello do
61
+ admin.hello
62
+ end
63
+ end
64
+
65
+ class Hello
66
+ def hello
67
+ 'hello'
68
+ end
69
+ end
70
+
71
+ describe 'interfaces' do
72
+ let(:context){
73
+ InterfaceContext.new(Hello.new, Hello.new)
74
+ }
75
+ it 'sets interface objects to use interface methods before singleton methods' do
76
+ assert_equal 'hello from admin', context.admin_hello
77
+ end
78
+
79
+ it 'marks the inteface constant private' do
80
+ error = assert_raises(NameError){
81
+ InterfaceContext::AdminInterface
82
+ }
83
+ assert_match /private constant/, error.message
84
+ end
85
+
86
+ it 'creates a private accessor method' do
87
+ assert_respond_to(context, :admin)
88
+ end
89
+ end
90
+ end
91
+
92
+ describe 'unknown' do
93
+ it 'raises an error' do
94
+ assert_raises(Surrounded::Context::InvalidRoleType){
95
+ class UnknownRole
96
+ extend Surrounded::Context
97
+
98
+ role :admin, :unknown do
99
+ end
100
+ end
101
+ }
102
+ end
103
+ end
104
+
105
+ describe 'custom default' do
106
+ it 'allows the use of custom default role types' do
107
+ class CustomDefaultWrap
108
+ extend Surrounded::Context
109
+
110
+ self.default_role_type = :wrap
111
+ apply_roles_on(:initialize)
112
+
113
+ initialize(:admin)
114
+
115
+ role :admin do
116
+ end
117
+ end
118
+ context = CustomDefaultWrap.new(Object.new)
119
+ assert_kind_of(SimpleDelegator, context.send(:admin))
120
+ end
121
+
122
+ it 'allows the setting of custom default role time for all Surrounded::Contexts' do
123
+ begin
124
+ old_default = Surrounded::Context.default_role_type
125
+ Surrounded::Context.default_role_type = :wrap
126
+ class CustomGlobalDefault
127
+ extend Surrounded::Context
128
+ apply_roles_on(:initialize)
129
+
130
+ initialize(:admin)
131
+
132
+ role :admin do
133
+ end
134
+ end
135
+
136
+ context = CustomGlobalDefault.new(Object.new)
137
+ assert_kind_of(SimpleDelegator, context.send(:admin))
138
+ ensure
139
+ Surrounded::Context.default_role_type = old_default
140
+ end
141
+ end
142
+ end
143
+ 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.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - '''Jim Gay'''
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-07-21 00:00:00.000000000 Z
11
+ date: 2013-08-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: triad
@@ -81,8 +81,8 @@ files:
81
81
  - README.md
82
82
  - Rakefile
83
83
  - examples/rails.rb
84
- - examples/wrappers.rb
85
84
  - lib/surrounded.rb
85
+ - lib/surrounded/README.md
86
86
  - lib/surrounded/context.rb
87
87
  - lib/surrounded/context/negotiator.rb
88
88
  - lib/surrounded/context/role_map.rb
@@ -92,6 +92,7 @@ files:
92
92
  - test/example_proxy_test.rb
93
93
  - test/example_threaded_test.rb
94
94
  - test/example_wrapper_test.rb
95
+ - test/role_context_method_test.rb
95
96
  - test/surrounded_context_test.rb
96
97
  - test/surrounded_test.rb
97
98
  - test/test_helper.rb
@@ -123,6 +124,7 @@ test_files:
123
124
  - test/example_proxy_test.rb
124
125
  - test/example_threaded_test.rb
125
126
  - test/example_wrapper_test.rb
127
+ - test/role_context_method_test.rb
126
128
  - test/surrounded_context_test.rb
127
129
  - test/surrounded_test.rb
128
130
  - test/test_helper.rb
data/examples/wrappers.rb DELETED
@@ -1,39 +0,0 @@
1
- # If you want to use wrappers, here's how you could
2
-
3
- require 'surrounded/context'
4
- require 'delegate'
5
- class WrapperContext
6
- extend Surrounded::Context
7
-
8
- apply_roles_on(:trigger)
9
- setup(:admin, :task)
10
-
11
- class Admin < SimpleDelegator
12
- def some_admin_method
13
- puts 'hello from the admin wrapper!'
14
- end
15
- end
16
-
17
- trigger :do_something do
18
- admin.some_admin_method
19
- end
20
-
21
- private
22
-
23
- def add_role_methods(obj, wrapper)
24
- assign_role(role_name(wrapper), wrapper.new(obj))
25
- end
26
-
27
- def remove_role_methods(obj, wrapper)
28
- # in this case, the obj is already wrapped
29
- core_object = self.send(role_name(wrapper)).__getobj__
30
- assign_role(role_name(wrapper), core_object)
31
- end
32
-
33
- def role_name(klass)
34
- klass.name.split("::").last.gsub(/([A-Z])/){ "_#{$1.downcase}" }.sub(/^_/,'')
35
- end
36
- end
37
-
38
- context = WrapperContext.new(Object.new, Object.new)
39
- context.do_something