surrounded 0.9.1 → 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -0
- data/README.md +12 -5
- data/lib/surrounded/access_control.rb +1 -1
- data/lib/surrounded/context/initializing.rb +20 -1
- data/lib/surrounded/context/negotiator.rb +31 -10
- data/lib/surrounded/context/role_builders.rb +12 -8
- data/lib/surrounded/context/role_map.rb +21 -1
- data/lib/surrounded/context.rb +38 -4
- data/lib/surrounded/version.rb +1 -1
- data/lib/surrounded.rb +3 -7
- data/test/example_proxy_test.rb +8 -3
- data/test/role_context_method_test.rb +1 -1
- data/test/surrounded_context_test.rb +19 -0
- data/test/surrounded_test.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f7ef86c7f64ee33fa79398d58bbdcb13ed3f8e4e
|
4
|
+
data.tar.gz: 9f65308ecb76695601b51fa5a7f745b21e7ca866
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2f83e74f34de171f5f66170b9f481624a4a148ce448fb880c59318dc9d2f9389e25321dc6552ba0c8058ec475a67be5bc297d82453f3a3f0e37cbe8a367e993d
|
7
|
+
data.tar.gz: 84e3aa18ddaf6b1b30c60897152e5dab28fe545df81566a3bd3c40cbbee3b7108e5a7bdfcb3c4a70520c9a913bac64ad27ee716d85b6d32cd696e0069ef87fb3
|
data/.travis.yml
CHANGED
data/README.md
CHANGED
@@ -6,9 +6,7 @@
|
|
6
6
|
[![Coverage Status](https://coveralls.io/repos/saturnflyer/surrounded/badge.png)](https://coveralls.io/r/saturnflyer/surrounded)
|
7
7
|
[![Gem Version](https://badge.fury.io/rb/surrounded.png)](http://badge.fury.io/rb/surrounded)
|
8
8
|
|
9
|
-
|
10
|
-
|
11
|
-
Surrounded is designed to help you better manage your business logic.
|
9
|
+
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.
|
12
10
|
|
13
11
|
## How to think about your objects
|
14
12
|
|
@@ -543,10 +541,12 @@ Using the `role` method to define modules and classes takes care of the setup fo
|
|
543
541
|
end
|
544
542
|
```
|
545
543
|
|
546
|
-
The `:interface` option is a special object which has all of
|
544
|
+
The `:interface` option is a special object which has all of the standard Object 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.
|
547
545
|
|
548
546
|
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__`.
|
549
547
|
|
548
|
+
The downside of using an interface is that it is still a wrapper. All of your defined role methods are executed in the context of the object playing the role, but the interface has it's own identity.
|
549
|
+
|
550
550
|
If you'd like to choose one and use it all the time, you can set the default:
|
551
551
|
|
552
552
|
```ruby
|
@@ -636,6 +636,13 @@ class ActiviatingAccount
|
|
636
636
|
# these also must be done if you create your own initialize method.
|
637
637
|
# this is a shortcut for using attr_reader and private
|
638
638
|
private_attr_reader :activator, :account
|
639
|
+
|
640
|
+
# initialize with keyword arguments
|
641
|
+
keyword_initialize(:activator, :account)
|
642
|
+
# this makes the following instance method signature with required keyword arguments
|
643
|
+
def initialize(activator:, account:)
|
644
|
+
# ...
|
645
|
+
end
|
639
646
|
|
640
647
|
role :activator do # module by default
|
641
648
|
def some_behavior; end
|
@@ -724,7 +731,7 @@ end
|
|
724
731
|
|
725
732
|
## Dependencies
|
726
733
|
|
727
|
-
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.
|
734
|
+
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.
|
728
735
|
|
729
736
|
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.
|
730
737
|
|
@@ -10,13 +10,32 @@ module Surrounded
|
|
10
10
|
line = __LINE__
|
11
11
|
mod.class_eval "
|
12
12
|
def initialize(#{setup_args.join(',')})
|
13
|
-
@role_map =
|
13
|
+
@role_map = role_mapper_class.new
|
14
14
|
map_roles(#{setup_args.to_s}.zip([#{setup_args.join(',')}]))
|
15
15
|
end
|
16
16
|
", __FILE__, line
|
17
17
|
const_set("ContextInitializer", mod)
|
18
18
|
include mod
|
19
19
|
end
|
20
|
+
|
21
|
+
def keyword_initialize(*setup_args)
|
22
|
+
private_attr_reader(*setup_args)
|
23
|
+
|
24
|
+
parameters = setup_args.map{|a| "#{a}:"}.join(',')
|
25
|
+
puts parameters
|
26
|
+
|
27
|
+
mod = Module.new
|
28
|
+
line = __LINE__
|
29
|
+
mod.class_eval %{
|
30
|
+
def initialize(#{parameters})
|
31
|
+
@role_map = role_mapper_class.new
|
32
|
+
map_roles(#{setup_args.to_s}.zip([#{setup_args.join(',')}]))
|
33
|
+
end
|
34
|
+
}, __FILE__, line
|
35
|
+
const_set("ContextInitializer", mod)
|
36
|
+
include mod
|
37
|
+
end
|
38
|
+
alias initialize_with_keywords keyword_initialize
|
20
39
|
end
|
21
40
|
end
|
22
41
|
end
|
@@ -1,12 +1,31 @@
|
|
1
1
|
module Surrounded
|
2
2
|
module Context
|
3
3
|
class Negotiator
|
4
|
+
class << self
|
5
|
+
# Return a class which has methods defined to forward the method to
|
6
|
+
# the wrapped object delegating to the behavior module.
|
7
|
+
# This prevents hits to method_missing.
|
8
|
+
def for_role(mod)
|
9
|
+
klass = Class.new(self)
|
10
|
+
mod.instance_methods(false).each do |meth|
|
11
|
+
num = __LINE__; klass.class_eval %{
|
12
|
+
def #{meth}(*args, &block)
|
13
|
+
@behaviors.instance_method(:#{meth}).bind(@object).call(*args, &block)
|
14
|
+
end
|
15
|
+
}, __FILE__, num
|
16
|
+
end
|
17
|
+
klass
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
|
4
22
|
identity = "__send__|object_id"
|
5
23
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
24
|
+
# Remove all methods except the identity methods
|
25
|
+
instance_methods.reject{ |m|
|
26
|
+
m.to_s =~ /#{identity}/
|
27
|
+
}.each do |meth|
|
28
|
+
undef_method meth
|
10
29
|
end
|
11
30
|
|
12
31
|
private
|
@@ -16,15 +35,17 @@ module Surrounded
|
|
16
35
|
end
|
17
36
|
|
18
37
|
def method_missing(meth, *args, &block)
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
end
|
38
|
+
@object.send(meth, *args, &block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def respond_to_missing?(meth, include_private=false)
|
42
|
+
@object.respond_to?(meth, include_private)
|
25
43
|
end
|
26
44
|
end
|
27
45
|
|
46
|
+
# The method_missing definition from Surrounded will apply
|
47
|
+
# before the one defined above. This allows the methods for
|
48
|
+
# the objects in the context to work properly
|
28
49
|
Negotiator.send(:prepend, Surrounded)
|
29
50
|
end
|
30
51
|
end
|
@@ -5,7 +5,7 @@ module Surrounded
|
|
5
5
|
# Define behaviors for your role players
|
6
6
|
def role(name, type=default_role_type, &block)
|
7
7
|
if type == :module
|
8
|
-
mod_name =
|
8
|
+
mod_name = RoleName(name)
|
9
9
|
mod = Module.new(&block).send(:include, ::Surrounded)
|
10
10
|
private_const_set(mod_name, mod)
|
11
11
|
else
|
@@ -20,7 +20,7 @@ module Surrounded
|
|
20
20
|
# Create a named behavior for a role using the standard library SimpleDelegator.
|
21
21
|
def wrap(name, &block)
|
22
22
|
require 'delegate'
|
23
|
-
wrapper_name =
|
23
|
+
wrapper_name = RoleName(name)
|
24
24
|
klass = private_const_set(wrapper_name, Class.new(SimpleDelegator, &block))
|
25
25
|
klass.send(:include, Surrounded)
|
26
26
|
end
|
@@ -30,7 +30,7 @@ module Surrounded
|
|
30
30
|
# This ties the implementation of the role to a specific class or module API.
|
31
31
|
def delegate_class(name, class_name, &block)
|
32
32
|
require 'delegate'
|
33
|
-
wrapper_name =
|
33
|
+
wrapper_name = RoleName(name)
|
34
34
|
klass = private_const_set(wrapper_name, DelegateClass(Object.const_get(class_name.to_s)))
|
35
35
|
klass.class_eval(&block)
|
36
36
|
klass.send(:include, Surrounded)
|
@@ -38,19 +38,23 @@ module Surrounded
|
|
38
38
|
|
39
39
|
# Create an object which will bind methods to the role player
|
40
40
|
def interface(name, &block)
|
41
|
-
|
42
|
-
|
41
|
+
# AdminInterface
|
42
|
+
interface_name = RoleName(name, 'Interface')
|
43
43
|
behavior = private_const_set(interface_name, Module.new(&block))
|
44
44
|
|
45
45
|
require 'surrounded/context/negotiator'
|
46
46
|
undef_method(name)
|
47
|
+
|
48
|
+
# AdminInterfaceProxy
|
49
|
+
proxy = private_const_set(RoleName(interface_name, 'Proxy'), Negotiator.for_role(behavior))
|
47
50
|
define_method(name) do
|
48
|
-
instance_variable_set("@#{name}",
|
51
|
+
instance_variable_set("@#{name}", proxy.new(role_map.assigned_player(name), behavior))
|
49
52
|
end
|
50
53
|
end
|
51
54
|
|
52
|
-
|
53
|
-
|
55
|
+
private
|
56
|
+
def RoleName(text, suffix=nil)
|
57
|
+
RoleName.new(text, suffix)
|
54
58
|
end
|
55
59
|
|
56
60
|
end
|
@@ -2,7 +2,27 @@ require 'triad'
|
|
2
2
|
require 'surrounded/context_errors'
|
3
3
|
module Surrounded
|
4
4
|
module Context
|
5
|
-
class RoleMap
|
5
|
+
class RoleMap
|
6
|
+
|
7
|
+
class << self
|
8
|
+
def from_base(klass=::Triad)
|
9
|
+
role_mapper = Class.new(::Surrounded::Context::RoleMap)
|
10
|
+
num = __LINE__; role_mapper.class_eval %{
|
11
|
+
def container
|
12
|
+
@container ||= #{klass}.new
|
13
|
+
end
|
14
|
+
}, __FILE__, num
|
15
|
+
%w{ update each values keys }.each do |meth|
|
16
|
+
num = __LINE__; role_mapper.class_eval %{
|
17
|
+
def #{meth}(*args, &block)
|
18
|
+
container.send(:#{meth}, *args, &block)
|
19
|
+
end
|
20
|
+
}, __FILE__, num
|
21
|
+
end
|
22
|
+
role_mapper
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
6
26
|
def role?(role)
|
7
27
|
keys.include?(role)
|
8
28
|
end
|
data/lib/surrounded/context.rb
CHANGED
@@ -88,6 +88,14 @@ module Surrounded
|
|
88
88
|
end
|
89
89
|
end
|
90
90
|
|
91
|
+
# Allow alternative implementations for the role map
|
92
|
+
# This requires that the specified mapper klass have an
|
93
|
+
# initializer method called 'from_base' which accepts a
|
94
|
+
# class name used to initialize the base object
|
95
|
+
def role_mapper_class(mapper: RoleMap, base: ::Triad)
|
96
|
+
@role_mapper_class ||= mapper.from_base(base)
|
97
|
+
end
|
98
|
+
|
91
99
|
module InstanceMethods
|
92
100
|
# Check whether a given name is a role inside the context.
|
93
101
|
# The provided block is used to evaluate whether or not the caller
|
@@ -111,7 +119,7 @@ module Surrounded
|
|
111
119
|
private
|
112
120
|
|
113
121
|
def role_map
|
114
|
-
@role_map ||=
|
122
|
+
@role_map ||= role_mapper_class.new
|
115
123
|
end
|
116
124
|
|
117
125
|
def map_roles(role_object_array)
|
@@ -189,14 +197,14 @@ module Surrounded
|
|
189
197
|
def apply_behaviors
|
190
198
|
role_map.each do |role, mod_name, object|
|
191
199
|
player = apply_behavior(role, mod_name, object)
|
192
|
-
player.
|
200
|
+
player.__send__(:store_context) do; end
|
193
201
|
end
|
194
202
|
end
|
195
203
|
|
196
204
|
def remove_behaviors
|
197
205
|
role_map.each do |role, mod_name, player|
|
198
206
|
if player.respond_to?(:remove_context, true)
|
199
|
-
player.
|
207
|
+
player.__send__(:remove_context) do; end
|
200
208
|
end
|
201
209
|
remove_behavior(role, mod_name, player)
|
202
210
|
end
|
@@ -223,7 +231,7 @@ module Surrounded
|
|
223
231
|
end
|
224
232
|
|
225
233
|
def role_behavior_name(role)
|
226
|
-
|
234
|
+
RoleName.new(role)
|
227
235
|
end
|
228
236
|
|
229
237
|
def role_module_basename(mod)
|
@@ -238,6 +246,10 @@ module Surrounded
|
|
238
246
|
self.class.send(:role_const_defined?, name)
|
239
247
|
end
|
240
248
|
|
249
|
+
def role_mapper_class
|
250
|
+
self.class.send(:role_mapper_class)
|
251
|
+
end
|
252
|
+
|
241
253
|
def singularize_name(name)
|
242
254
|
if name.respond_to?(:singularize)
|
243
255
|
name.singularize
|
@@ -253,5 +265,27 @@ module Surrounded
|
|
253
265
|
end
|
254
266
|
end
|
255
267
|
end
|
268
|
+
|
269
|
+
class RoleName
|
270
|
+
def initialize(string, suffix=nil)
|
271
|
+
@string = string.
|
272
|
+
to_s.
|
273
|
+
split(/_/).
|
274
|
+
map{|part|
|
275
|
+
part.capitalize
|
276
|
+
}.
|
277
|
+
join.
|
278
|
+
sub(/_\d+/,'') + suffix.to_s
|
279
|
+
end
|
280
|
+
|
281
|
+
def to_str
|
282
|
+
@string
|
283
|
+
end
|
284
|
+
alias to_s to_str
|
285
|
+
|
286
|
+
def to_sym
|
287
|
+
@string.to_sym
|
288
|
+
end
|
289
|
+
end
|
256
290
|
end
|
257
291
|
end
|
data/lib/surrounded/version.rb
CHANGED
data/lib/surrounded.rb
CHANGED
@@ -11,19 +11,15 @@ module Surrounded
|
|
11
11
|
|
12
12
|
private
|
13
13
|
|
14
|
-
def store_context(
|
14
|
+
def store_context(&block)
|
15
15
|
accessor = block.binding.eval('self')
|
16
|
-
|
17
|
-
surroundings.unshift(ctxt)
|
18
|
-
end
|
16
|
+
surroundings.unshift(accessor)
|
19
17
|
self
|
20
18
|
end
|
21
19
|
|
22
20
|
def remove_context(&block)
|
23
21
|
accessor = block.binding.eval('self')
|
24
|
-
if
|
25
|
-
surroundings.shift
|
26
|
-
end
|
22
|
+
surroundings.shift if surroundings.include?(accessor)
|
27
23
|
self
|
28
24
|
end
|
29
25
|
|
data/test/example_proxy_test.rb
CHANGED
@@ -36,8 +36,13 @@ class ProxyContext
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
-
ProxyUser =
|
40
|
-
|
39
|
+
ProxyUser = Class.new do
|
40
|
+
include Surrounded
|
41
|
+
def initialize(name)
|
42
|
+
@name = name
|
43
|
+
end
|
44
|
+
attr_reader :name
|
45
|
+
end
|
41
46
|
|
42
47
|
describe ProxyContext do
|
43
48
|
let(:user){
|
@@ -60,7 +65,7 @@ describe ProxyContext do
|
|
60
65
|
it 'passes missing methods up the ancestry of the object' do
|
61
66
|
err = ->{ context.admin_missing_method }.must_raise(NoMethodError)
|
62
67
|
|
63
|
-
assert_match
|
68
|
+
assert_match /ProxyUser.*name="Jim"/, err.message
|
64
69
|
end
|
65
70
|
|
66
71
|
it 'allows access to other objects in the context' do
|
@@ -258,3 +258,22 @@ describe Surrounded::Context, 'auto-assigning roles for collections' do
|
|
258
258
|
assert_equal "member show, member show", context.get_member_show
|
259
259
|
end
|
260
260
|
end
|
261
|
+
|
262
|
+
class Keyworder
|
263
|
+
extend Surrounded::Context
|
264
|
+
|
265
|
+
keyword_initialize :this, :that
|
266
|
+
end
|
267
|
+
|
268
|
+
describe Surrounded::Context, 'keyword initializers' do
|
269
|
+
it 'works with keyword arguments' do
|
270
|
+
assert context = Keyworder.new(this: User.new('Jim'), that: User.new('Guille'))
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'raises errors with missing keywords' do
|
274
|
+
err = assert_raises(ArgumentError){
|
275
|
+
Keyworder.new(this: User.new('Amy'))
|
276
|
+
}
|
277
|
+
assert_match /missing keyword: that/, err.message
|
278
|
+
end
|
279
|
+
end
|
data/test/surrounded_test.rb
CHANGED
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.
|
4
|
+
version: 0.9.2
|
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-
|
11
|
+
date: 2015-05-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: triad
|