surrounded 0.2.1 → 0.3.0

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: 004d807c4309fea2abc8847f5c23aba8994f6829
4
- data.tar.gz: 3a42c0a074f5b1e3f179eb3763ab21a36afbcc19
3
+ metadata.gz: 3e6e141d7aa5fdf78d69d250369fa15f2f6b5f6d
4
+ data.tar.gz: 5251f5c762065460afa6da69f0da2bf2f68529dc
5
5
  SHA512:
6
- metadata.gz: 59292a0a8d86b9703e2364df3416613af3ab18695ee6afa064b4597f57c3c0489e0c551bebe00edc6d19b8d9c618f541d78aec23dbc42cc9d81e9f9b9e448755
7
- data.tar.gz: f4eb083c7d739c79967aef23e57436770eaadc16ea6a1b30b19cd5a0e8eb59c1a521c7d3733cec411b77b97363426409990b012a5c8b9afd4abe1c194af8208d
6
+ metadata.gz: 7188a2fadebd4d332f402c8fba1cabb77968623a698a02a083ef59266a1ee8b4223216f5aa70d9c5886ddc8af1fef9058686a67ce512ab94e4571cc42e23da39
7
+ data.tar.gz: 0ca5c9b9ead86e6d87d0209c5fa0e59cb4b67ac957c0d0425f85704270f1879702cade79de3af98323df85ff1dc9fe2a3a22a7a19e789adf58bcdda534ea89d9
data/README.md CHANGED
@@ -71,7 +71,7 @@ If you're going to be doing this a lot, it's painful. Here's what `Surrounded` d
71
71
  class MyEnvironment
72
72
  extend Surrounded::Context
73
73
 
74
- setup(:employee, :boss)
74
+ initialize(:employee, :boss)
75
75
 
76
76
  module Employee
77
77
  # extra behavior here...
@@ -97,7 +97,7 @@ With that, all instances of `User` have implicit access to their surroundings.
97
97
 
98
98
  _Yeah... How?_
99
99
 
100
- Via `method_missing` those `User` instances can access a `context` object stored in `Thread.current`. I didn't mention how the context is set, however.
100
+ Via `method_missing` those `User` instances can access a `context` object it stores in a `@__surroundings__` collection. I didn't mention how the context is set, however.
101
101
 
102
102
  Your environment will have methods of it's own that will trigger actions on the objects inside, but we need those trigger methods to set the environment instance as the current context so that the objects it contains can access them.
103
103
 
@@ -192,7 +192,7 @@ class ActiviatingAccount
192
192
  apply_roles_on(:trigger) # this is the default
193
193
  # apply_roles_on(:initialize) # set this to apply behavior from the start
194
194
 
195
- setup(:activator, :account)
195
+ initialize(:activator, :account)
196
196
 
197
197
  module Activator
198
198
  def some_behavior; end
@@ -228,6 +228,38 @@ context.do_something
228
228
  current_user.some_behavior # NoMethodError
229
229
  ```
230
230
 
231
+ ## How's the performance?
232
+
233
+ I haven't really tested yet, but there are several ways you can add behavior to your objects.
234
+
235
+ There are a few defaults built in.
236
+
237
+ 1. If you define modules for the added behavior, the code will run `object.extend(RoleInterface)`
238
+ 2. If you are using [casting](http://github.com/saturnflyer/casting), the code will run `object.cast_as(RoleInterface)`
239
+ 3. If you would rather use wrappers you can define classes and the code will run `RoleInterface.new(object)` and assumes that the `new` method takes 1 argument. You'll need to remember to `include Surrounded` in your classes, however.
240
+ 4. If you want to use wrappers but would rather not muck about with including modules and whatnot, you can define them like this:
241
+
242
+ ```
243
+ class SomeContext
244
+ extend Surrounded::Context
245
+
246
+ initialize(:admin, :user)
247
+
248
+ wrap :admin do
249
+ # special methods defined here
250
+ end
251
+ ```
252
+
253
+ The `wrap` method will create a class of the given name (`Admin` in this case) and will inherit from `SimpleDelegator` from the Ruby standard library _and_ will `include Surrounded`.
254
+
255
+ Lastly, there's a 5th option if you're using Ruby 2.x: `interface`.
256
+
257
+ The `interface` method acts similarly to the `wrap` method in that it returns an object that is not actually the object you want. But an `interface` is different in that it will apply methods from a module instead of using methods defined in a SimpleDelegator subclass. How is that important? Well you are free to use things like instance variables in your methods because they will be executed in the context of the object. This is unlike methods in a SimpleDelegator where the wrapper maintains its own instance variables.
258
+
259
+ _Which should I use?_
260
+
261
+ Start with the default and see how it goes, then try another approach and measure the changes.
262
+
231
263
  ## Dependencies
232
264
 
233
265
  The dependencies are minimal. The plan is to keep it that way but allow you to configure things as you need.
@@ -1,5 +1,7 @@
1
1
  require 'set'
2
2
  require 'surrounded/context/role_map'
3
+ require 'redcard'
4
+
3
5
  module Surrounded
4
6
  module Context
5
7
  def self.extended(base)
@@ -7,12 +9,14 @@ module Surrounded
7
9
  base.singleton_class.send(:alias_method, :setup, :initialize)
8
10
  end
9
11
 
10
- def triggers
11
- @triggers.dup
12
+ def new(*)
13
+ instance = super
14
+ instance.instance_variable_set('@__apply_role_policy', __apply_role_policy)
15
+ instance
12
16
  end
13
17
 
14
- def policy
15
- @policy ||= :trigger
18
+ def triggers
19
+ @triggers.dup
16
20
  end
17
21
 
18
22
  private
@@ -24,8 +28,26 @@ module Surrounded
24
28
  klass.send(:include, Surrounded)
25
29
  end
26
30
 
31
+ if RedCard.check '2.0'
32
+ def interface(name, &block)
33
+ class_basename = name.to_s.gsub(/(?:^|_)([a-z])/){ $1.upcase }
34
+ interface_name = class_basename + 'Interface'
35
+
36
+ behavior = const_set(interface_name, Module.new(&block))
37
+
38
+ require 'surrounded/context/negotiator'
39
+ define_method(name) do
40
+ instance_variable_set("@#{name}", Negotiator.new(role_map.assigned_player(name), behavior))
41
+ end
42
+ end
43
+ end
44
+
27
45
  def apply_roles_on(which)
28
- @policy = which
46
+ @__apply_role_policy = which
47
+ end
48
+
49
+ def __apply_role_policy
50
+ @__apply_role_policy ||= :trigger
29
51
  end
30
52
 
31
53
  def initialize(*setup_args)
@@ -33,9 +55,10 @@ module Surrounded
33
55
 
34
56
  class_eval "
35
57
  def initialize(#{setup_args.join(',')})
58
+ @__apply_role_policy = :#{__apply_role_policy}
36
59
  arguments = method(__method__).parameters.map{|arg| eval(arg[1].to_s) }
37
60
  map_roles(#{setup_args}.zip(arguments))
38
- apply_roles if policy == :initialize
61
+ apply_roles if __apply_role_policy == :initialize
39
62
  end
40
63
  "
41
64
  end
@@ -54,12 +77,12 @@ module Surrounded
54
77
 
55
78
  define_method(name, *args){
56
79
  begin
57
- apply_roles if policy == :trigger
80
+ apply_roles if __apply_role_policy == :trigger
58
81
 
59
82
  self.send("trigger_#{name}", *args)
60
83
 
61
84
  ensure
62
- remove_roles if policy == :trigger
85
+ remove_roles if __apply_role_policy == :trigger
63
86
  end
64
87
  }
65
88
  end
@@ -97,8 +120,8 @@ module Surrounded
97
120
  role_map.update(role, mod_name, object)
98
121
  end
99
122
 
100
- def policy
101
- @policy ||= self.class.policy
123
+ def __apply_role_policy
124
+ @__apply_role_policy
102
125
  end
103
126
 
104
127
  def add_interface(role, behavior, object)
@@ -0,0 +1,28 @@
1
+ module Surrounded
2
+ module Context
3
+ class Negotiator
4
+ identity = "__send__|object_id"
5
+
6
+ instance_methods.each do |meth|
7
+ unless meth.to_s =~ /#{identity}/
8
+ undef_method meth
9
+ end
10
+ end
11
+
12
+ private
13
+
14
+ def initialize(object, behavior)
15
+ @object, @behavior = object, behavior
16
+ end
17
+
18
+ def method_missing(meth, *args, &block)
19
+ if @behavior.instance_methods.include?(meth)
20
+ the_method = @behavior.instance_method(meth)
21
+ the_method.bind(@object).call(*args, &block)
22
+ else
23
+ @object.send(meth, *args, &block)
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,3 @@
1
1
  module Surrounded
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
data/surrounded.gemspec CHANGED
@@ -18,7 +18,8 @@ Gem::Specification.new do |spec|
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
21
- spec.add_dependency "triad", "~> 0.1.2"
21
+ spec.add_dependency "triad", "~> 0.1.2"
22
+ spec.add_dependency "redcard","~> 1.1.0"
22
23
 
23
24
  spec.add_development_dependency "bundler", "~> 1.3"
24
25
  spec.add_development_dependency "rake"
@@ -0,0 +1,55 @@
1
+ require 'test_helper'
2
+
3
+ if RedCard.check '2.0'
4
+
5
+ # If you want to use wrappers, here's how you could
6
+ class ProxyContext
7
+ extend Surrounded::Context
8
+
9
+ apply_roles_on(:trigger)
10
+ initialize(:admin, :task)
11
+
12
+ interface :admin do
13
+ def some_admin_method
14
+ "hello from #{name}, the admin interface!"
15
+ end
16
+ end
17
+
18
+ trigger :do_something do
19
+ admin.some_admin_method
20
+ end
21
+
22
+ trigger :admin_name do
23
+ admin.name
24
+ end
25
+
26
+ trigger :admin_missing_method do
27
+ admin.method_that_does_not_exist
28
+ end
29
+ end
30
+
31
+ ProxyUser = Struct.new(:name)
32
+
33
+ describe ProxyContext do
34
+ let(:user){
35
+ ProxyUser.new('Jim')
36
+ }
37
+ let(:context){
38
+ ProxyContext.new(user, Object.new)
39
+ }
40
+ it 'proxys methods between objects and its interface' do
41
+ assert_equal 'hello from Jim, the admin interface!', context.do_something
42
+ end
43
+
44
+ it 'forwards methods that the object responds to' do
45
+ assert_equal 'Jim', context.admin_name
46
+ end
47
+
48
+ it 'passes missing methods up the ancestry of the object' do
49
+ err = ->{ context.admin_missing_method }.must_raise(NoMethodError)
50
+
51
+ assert_match 'ProxyUser name="Jim"', err.message
52
+ end
53
+ end
54
+
55
+ end # RedCard.check '2.0'
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.2.1
4
+ version: 0.3.0
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-10 00:00:00.000000000 Z
11
+ date: 2013-07-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: triad
@@ -24,6 +24,20 @@ dependencies:
24
24
  - - ~>
25
25
  - !ruby/object:Gem::Version
26
26
  version: 0.1.2
27
+ - !ruby/object:Gem::Dependency
28
+ name: redcard
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: 1.1.0
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ~>
39
+ - !ruby/object:Gem::Version
40
+ version: 1.1.0
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: bundler
29
43
  requirement: !ruby/object:Gem::Requirement
@@ -70,10 +84,12 @@ files:
70
84
  - examples/wrappers.rb
71
85
  - lib/surrounded.rb
72
86
  - lib/surrounded/context.rb
87
+ - lib/surrounded/context/negotiator.rb
73
88
  - lib/surrounded/context/role_map.rb
74
89
  - lib/surrounded/context_errors.rb
75
90
  - lib/surrounded/version.rb
76
91
  - surrounded.gemspec
92
+ - test/example_proxy_test.rb
77
93
  - test/example_threaded_test.rb
78
94
  - test/example_wrapper_test.rb
79
95
  - test/surrounded_context_test.rb
@@ -104,6 +120,7 @@ signing_key:
104
120
  specification_version: 4
105
121
  summary: Create encapsulated environments for your objects.
106
122
  test_files:
123
+ - test/example_proxy_test.rb
107
124
  - test/example_threaded_test.rb
108
125
  - test/example_wrapper_test.rb
109
126
  - test/surrounded_context_test.rb