surrounded 0.3.0 → 0.3.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 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