surrounded 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +19 -0
- data/.simplecov +3 -0
- data/.travis.yml +15 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +205 -0
- data/Rakefile +11 -0
- data/examples/rails.rb +65 -0
- data/lib/surrounded.rb +24 -0
- data/lib/surrounded/context.rb +86 -0
- data/lib/surrounded/version.rb +3 -0
- data/surrounded.gemspec +23 -0
- data/test/surrounded_context_test.rb +161 -0
- data/test/surrounded_test.rb +43 -0
- data/test/test_helper.rb +25 -0
- metadata +90 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: eec57f6f3717bca03404f2acde19afb3cb2a08d4
|
4
|
+
data.tar.gz: 7c6fef1028b60551bcc1cafdb5e7e9099d1ab7ef
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2fe9113c6aaed6c7a09c687045cb54ba792bc59521b8917b09fdbc43a7d17c065cb54eac1a437a1fabcd9d4f094e78e9d67bf75333553ffef6cc63b7d000524e
|
7
|
+
data.tar.gz: 6472c2aef734c56f55d9dc2a0f9992b7e4f693f2ff677685586a3fee3a872cb9bf2602f6cb05e9b68b82a040fc0bf2be32b6aec0c3d4f7ef849df02874a95f65
|
data/.gitignore
ADDED
data/.simplecov
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 'Jim Gay'
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
# Surrounded
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/saturnflyer/surrounded.png?branch=master)](https://travis-ci.org/saturnflyer/surrounded)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/saturnflyer/surrounded.png)](https://codeclimate.com/github/saturnflyer/surrounded)
|
5
|
+
[![Coverage Status](https://coveralls.io/repos/saturnflyer/surrounded/badge.png)](https://coveralls.io/r/saturnflyer/surrounded)
|
6
|
+
|
7
|
+
## Create encapsulated environments for your objects.
|
8
|
+
|
9
|
+
Keep the distraction of other features out of your way. Write use cases and focus on just the business logic
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Add `Surrounded` to your objects to give them awareness of other objects.
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
class User
|
17
|
+
include Surrounded
|
18
|
+
end
|
19
|
+
```
|
20
|
+
|
21
|
+
Now your user instances will be able to get objects in their environment.
|
22
|
+
|
23
|
+
_What environment!? I don't get it._
|
24
|
+
|
25
|
+
I didn't explain that yet.
|
26
|
+
|
27
|
+
You can make an object which contains other objects. It acts as an environment
|
28
|
+
and objects inside should have knowledge of the other objects in the environment.
|
29
|
+
Take a breath, because there's a lot going on.
|
30
|
+
|
31
|
+
First, you extend a class with the appropriate module to turn it into an object environment:
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
class MyEnvironment
|
35
|
+
extend Surrounded::Context
|
36
|
+
end
|
37
|
+
```
|
38
|
+
|
39
|
+
Typical initialization of this environment has a lot of code. For example:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
class MyEnvironment
|
43
|
+
extend Surrounded::Context
|
44
|
+
|
45
|
+
attr_reader :employee, :boss
|
46
|
+
private :employee, :boss
|
47
|
+
def initialize(employee, boss)
|
48
|
+
@employee = employee.extend(Exmployee)
|
49
|
+
@boss = boss
|
50
|
+
end
|
51
|
+
|
52
|
+
module Employee
|
53
|
+
# extra behavior here...
|
54
|
+
end
|
55
|
+
end
|
56
|
+
```
|
57
|
+
|
58
|
+
_WTF was all that!?_
|
59
|
+
|
60
|
+
Relax. I'll explain.
|
61
|
+
|
62
|
+
When you create an instance of `MyEnvironment` it has certain objects inside.
|
63
|
+
Here we see that it has an `employee` and a `boss`. Inside the methods of the environment it's simpler and easier to write `employee` instead of `@employee` so we make them `attr_reader`s. But we don't need these methods to be externally accessible so we set them to private.
|
64
|
+
|
65
|
+
Next, we want to add environment-specific behavior to the `employee` so we extend the object with the module `Employee`.
|
66
|
+
|
67
|
+
If you're going to be doing this a lot, it's painful. Here's what `Surrounded` does for you:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
class MyEnvironment
|
71
|
+
extend Surrounded::Context
|
72
|
+
|
73
|
+
setup(:employee, :boss)
|
74
|
+
|
75
|
+
module Employee
|
76
|
+
# extra behavior here...
|
77
|
+
end
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
There! All that boilerplate code is cleaned up.
|
82
|
+
|
83
|
+
Notice that there's no `Boss` module. If a module of that name does not exist, the object passed into initialize simply won't gain any new behavior.
|
84
|
+
|
85
|
+
_OK. I think I get it, but what about the objects? How are they aware of their environment? Isn't that what this is supposed to do?_
|
86
|
+
|
87
|
+
Yup. Ruby doesn't have a notion of a local environment, so we lean on `method_missing` to do the work for us.
|
88
|
+
|
89
|
+
```ruby
|
90
|
+
class User
|
91
|
+
include Surrounded
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
With that, all instances of `User` have implicit access to their surroundings.
|
96
|
+
|
97
|
+
_Yeah... How?_
|
98
|
+
|
99
|
+
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
|
+
|
101
|
+
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.
|
102
|
+
|
103
|
+
Here's an example of what we want:
|
104
|
+
|
105
|
+
```ruby
|
106
|
+
class MyEnvironment
|
107
|
+
# other stuff from above is still here...
|
108
|
+
|
109
|
+
def shove_it
|
110
|
+
Thread.current[:context] = self
|
111
|
+
employee.quit
|
112
|
+
Thread.current[:context] = nil
|
113
|
+
end
|
114
|
+
|
115
|
+
module Employee
|
116
|
+
def quit
|
117
|
+
say("I'm sick of this place, #{boss.name}!")
|
118
|
+
stomp
|
119
|
+
throw_papers
|
120
|
+
say("I quit!")
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
```
|
125
|
+
|
126
|
+
What's happening in there is that when the `shove_it` method is called, the current environment object is stored as the context.
|
127
|
+
|
128
|
+
The behavior defined in the `Employee` module assumes that it may access other objects in it's local environment. The `boss` object, for example, is never explicitly passed in as an argument.
|
129
|
+
|
130
|
+
_WTF!? That's insane!_
|
131
|
+
|
132
|
+
I thought so too, at first. But continually passing references assumes there's no relationship between objects in that method. What `Surrounded` does for us is to make the relationship between objects and gives them the ability to access each other.
|
133
|
+
|
134
|
+
This simple example may seem trivial, but the more contextual code you have the more cumbersome passing references becomes. By moving knowledge to the local environment, you're free to make changes to the procedures without the need to alter method signatures with new refrences or the removal of unused ones.
|
135
|
+
|
136
|
+
By using `Surrounded::Context` you are declaring a relationship between the objects inside.
|
137
|
+
|
138
|
+
Because all the behavior is defined internally and only relevant internally, those relationships don't exist outside of the environment.
|
139
|
+
|
140
|
+
_OK. I think I understand. So I can change business logic just by changing the procedures and the objects. I don't need to adjust arguments for a new requirement. That's kind of cool!_
|
141
|
+
|
142
|
+
Damn right.
|
143
|
+
|
144
|
+
But you don't want to continually set those `Thread` variables, do you?
|
145
|
+
|
146
|
+
_No. That's annoying._
|
147
|
+
|
148
|
+
Yeah. Instead, it would be easier to have this library do the work for us.
|
149
|
+
Here's what you can do:
|
150
|
+
|
151
|
+
```ruby
|
152
|
+
class MyEnvironment
|
153
|
+
# the other code from above...
|
154
|
+
|
155
|
+
trigger :shove_it do
|
156
|
+
employee.quit
|
157
|
+
end
|
158
|
+
end
|
159
|
+
```
|
160
|
+
|
161
|
+
By using this `trigger` keyword, our block is the code we care about, but internally the method is written to set the `Thread` variables.
|
162
|
+
|
163
|
+
_Hmm. I don't like having to do that._
|
164
|
+
|
165
|
+
Me either. I'd rather just use `def` but getting automatic code for setting the context is really convenient.
|
166
|
+
It also allows us to store the triggers so that you can, for example, provide details outside of the environment about what triggers exist.
|
167
|
+
|
168
|
+
```ruby
|
169
|
+
context = MyEnvironment.new(current_user, the_boss)
|
170
|
+
context.triggers #=> [:shove_it]
|
171
|
+
```
|
172
|
+
|
173
|
+
You might find that useful for dynamically defining user interfaces.
|
174
|
+
|
175
|
+
## Dependencies
|
176
|
+
|
177
|
+
The dependencies are minimal. The plan is to keep it that way but allow you to configure things as you need.
|
178
|
+
|
179
|
+
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.
|
180
|
+
|
181
|
+
## To Do
|
182
|
+
|
183
|
+
Casting provides a way to remove features outside of a block. For now, the code doesn't attempt to `uncast` an object. It will in the future though.
|
184
|
+
|
185
|
+
## Installation
|
186
|
+
|
187
|
+
Add this line to your application's Gemfile:
|
188
|
+
|
189
|
+
gem 'surrounded'
|
190
|
+
|
191
|
+
And then execute:
|
192
|
+
|
193
|
+
$ bundle
|
194
|
+
|
195
|
+
Or install it yourself as:
|
196
|
+
|
197
|
+
$ gem install surrounded
|
198
|
+
|
199
|
+
## Contributing
|
200
|
+
|
201
|
+
1. Fork it
|
202
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
203
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
204
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
205
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
data/examples/rails.rb
ADDED
@@ -0,0 +1,65 @@
|
|
1
|
+
# Here's an example of how you might use this in rails.
|
2
|
+
|
3
|
+
# First, be guarded against changes in third-party libraries
|
4
|
+
module Awareness
|
5
|
+
include Surrounded
|
6
|
+
include Casting::Client
|
7
|
+
delegate_missing_methods
|
8
|
+
end
|
9
|
+
|
10
|
+
class User
|
11
|
+
include Awareness
|
12
|
+
end
|
13
|
+
|
14
|
+
class ApplicationController
|
15
|
+
include Awareness
|
16
|
+
end
|
17
|
+
|
18
|
+
class SomeUseCase
|
19
|
+
extend Surrounded::Context
|
20
|
+
|
21
|
+
setup(:user, :other_user, :listener)
|
22
|
+
|
23
|
+
trigger :do_something do
|
24
|
+
user.something
|
25
|
+
end
|
26
|
+
|
27
|
+
module User
|
28
|
+
def something
|
29
|
+
puts "Hello, #{other_user}"
|
30
|
+
listener.redirect_to('/')
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class SomethingController < ApplicationController
|
36
|
+
use_case = :SomeUseCase
|
37
|
+
def create
|
38
|
+
use_case = :SomeOverrideUseCase
|
39
|
+
trigger :do_something, current_user, User.last
|
40
|
+
end
|
41
|
+
|
42
|
+
def trigger(meth, *args)
|
43
|
+
use_case.new(*(args << self)).send(meth)
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.use_case=(name)
|
47
|
+
@_default_use_case_name = name
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.use_case
|
51
|
+
@_default_use_case_name.to_s.constantize
|
52
|
+
end
|
53
|
+
|
54
|
+
def use_case=(name)
|
55
|
+
@_use_case_name = name
|
56
|
+
end
|
57
|
+
|
58
|
+
def use_case
|
59
|
+
if defined?(@_use_case_name)
|
60
|
+
@_use_case_name.to_s.constantize
|
61
|
+
else
|
62
|
+
self.class.use_case
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
data/lib/surrounded.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require "surrounded/version"
|
2
|
+
|
3
|
+
module Surrounded
|
4
|
+
private
|
5
|
+
|
6
|
+
def method_missing(meth, *args, &block)
|
7
|
+
context.role?(meth){} || super
|
8
|
+
end
|
9
|
+
|
10
|
+
def respond_to_missing?(meth, include_private=false)
|
11
|
+
!!context.role?(meth){} || super
|
12
|
+
end
|
13
|
+
|
14
|
+
def context
|
15
|
+
Thread.current[:context] ||= []
|
16
|
+
Thread.current[:context].first || NullContext.new
|
17
|
+
end
|
18
|
+
|
19
|
+
class NullContext < BasicObject
|
20
|
+
def role?(*args)
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
require 'set'
|
2
|
+
module Surrounded
|
3
|
+
module Context
|
4
|
+
def self.extended(base)
|
5
|
+
base.send(:include, InstanceMethods)
|
6
|
+
end
|
7
|
+
|
8
|
+
def triggers
|
9
|
+
@triggers.dup
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def setup(*setup_args)
|
15
|
+
attr_reader(*setup_args)
|
16
|
+
private(*setup_args)
|
17
|
+
|
18
|
+
define_method(:initialize){ |*args|
|
19
|
+
Hash[setup_args.zip(args)].each{ |role, object|
|
20
|
+
|
21
|
+
role_module_name = Context.classify_string(role)
|
22
|
+
klass = self.class
|
23
|
+
|
24
|
+
if mod = klass.const_defined?(role_module_name) && !mod.is_a?(Class)
|
25
|
+
object = Context.modify(object, klass.const_get(role_module_name))
|
26
|
+
end
|
27
|
+
|
28
|
+
roles[role.to_s] = object
|
29
|
+
instance_variable_set("@#{role}", object)
|
30
|
+
}
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
def trigger(name, *args, &block)
|
35
|
+
store_trigger(name)
|
36
|
+
|
37
|
+
define_method(:"trigger_#{name}", *args, &block)
|
38
|
+
|
39
|
+
private :"trigger_#{name}"
|
40
|
+
|
41
|
+
define_method(name, *args){
|
42
|
+
begin
|
43
|
+
(Thread.current[:context] ||= []).unshift(self)
|
44
|
+
self.send("trigger_#{name}", *args)
|
45
|
+
ensure
|
46
|
+
(Thread.current[:context] ||= []).shift
|
47
|
+
end
|
48
|
+
}
|
49
|
+
end
|
50
|
+
|
51
|
+
def store_trigger(name)
|
52
|
+
@triggers ||= Set.new
|
53
|
+
@triggers << name
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.classify_string(string)
|
57
|
+
string.to_s.gsub(/(?:^|_)([a-z])/) { $1.upcase }
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.modify(obj, mod)
|
61
|
+
return obj if mod.is_a?(Class)
|
62
|
+
if obj.respond_to?(:cast_as)
|
63
|
+
obj.cast_as(mod)
|
64
|
+
else
|
65
|
+
obj.extend(mod)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
module InstanceMethods
|
70
|
+
def role?(name, &block)
|
71
|
+
accessor = eval('self', block.binding)
|
72
|
+
roles.values.include?(accessor) && roles[name.to_s]
|
73
|
+
end
|
74
|
+
|
75
|
+
def triggers
|
76
|
+
self.class.triggers
|
77
|
+
end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def roles
|
82
|
+
@roles ||= {}
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
data/surrounded.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'surrounded/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "surrounded"
|
8
|
+
spec.version = Surrounded::VERSION
|
9
|
+
spec.authors = ["'Jim Gay'"]
|
10
|
+
spec.email = ["jim@saturnflyer.com"]
|
11
|
+
spec.description = %q{Gives an object implicit access to other objects in it's environment.}
|
12
|
+
spec.summary = %q{Create encapsulated environments for your objects.}
|
13
|
+
spec.homepage = "http://github.com/saturnflyer/surrounded"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
end
|
@@ -0,0 +1,161 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe Surrounded::Context, '#triggers' do
|
4
|
+
let(:user){ User.new("Jim") }
|
5
|
+
let(:other_user){ User.new("Guille") }
|
6
|
+
let(:context){ TestContext.new(user, other_user) }
|
7
|
+
|
8
|
+
it 'lists the externally accessible trigger methods' do
|
9
|
+
assert context.triggers.include?(:access_other_object)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'prevents altering the list of triggers externally' do
|
13
|
+
original_trigger_list = context.triggers
|
14
|
+
context.triggers << 'another_trigger'
|
15
|
+
assert_equal original_trigger_list, context.triggers
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe Surrounded::Context, '.triggers' do
|
20
|
+
it 'lists the externally accessible trigger methods' do
|
21
|
+
assert TestContext.triggers.include?(:access_other_object)
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'prevents altering the list of triggers externally' do
|
25
|
+
original_trigger_list = TestContext.triggers
|
26
|
+
TestContext.triggers << 'another_trigger'
|
27
|
+
assert_equal original_trigger_list, TestContext.triggers
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe Surrounded::Context, '.trigger' do
|
32
|
+
let(:user){ User.new("Jim") }
|
33
|
+
let(:other_user){ User.new("Guille") }
|
34
|
+
let(:context){ TestContext.new(user, other_user) }
|
35
|
+
|
36
|
+
it 'defines a public method on the context' do
|
37
|
+
assert context.respond_to?(:access_other_object)
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'gives objects access to each other inside the method' do
|
41
|
+
assert_raises(NoMethodError){
|
42
|
+
user.other_user
|
43
|
+
}
|
44
|
+
assert_equal "Guille", context.access_other_object
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe Surrounded::Context, '#role?' do
|
49
|
+
let(:user){
|
50
|
+
test_user = User.new("Jim")
|
51
|
+
|
52
|
+
def test_user.get_role(name, context)
|
53
|
+
context.role?(name){}
|
54
|
+
end
|
55
|
+
|
56
|
+
test_user
|
57
|
+
}
|
58
|
+
let(:other_user){ User.new("Guille") }
|
59
|
+
let(:external){
|
60
|
+
external_object = Object.new
|
61
|
+
def external_object.get_role_from_context(name, context)
|
62
|
+
context.role?(name){}
|
63
|
+
end
|
64
|
+
external_object
|
65
|
+
}
|
66
|
+
let(:context){ TestContext.new(user, other_user) }
|
67
|
+
|
68
|
+
it 'returns the object assigned to the named role' do
|
69
|
+
assert_equal user, user.get_role(:user, context)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'returns false if the role does not exist' do
|
73
|
+
refute user.get_role(:non_existant_role, context)
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'returns false if the accessing object is not a role player in the context' do
|
77
|
+
refute external.get_role_from_context(:user, context)
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'checks for the role based upon the calling object' do
|
81
|
+
refute context.role?(:user){} # this test is the caller
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
require 'casting'
|
86
|
+
CastingUser = Struct.new(:name)
|
87
|
+
class CastingUser
|
88
|
+
include Casting::Client
|
89
|
+
delegate_missing_methods
|
90
|
+
end
|
91
|
+
|
92
|
+
class RoleAssignmentContext
|
93
|
+
extend Surrounded::Context
|
94
|
+
|
95
|
+
setup(:user, :other_user)
|
96
|
+
|
97
|
+
trigger :user_ancestors do
|
98
|
+
user.singleton_class.ancestors
|
99
|
+
end
|
100
|
+
|
101
|
+
trigger :other_user_ancestors do
|
102
|
+
other_user.singleton_class.ancestors
|
103
|
+
end
|
104
|
+
|
105
|
+
trigger :check_user_response do
|
106
|
+
user.respond_to?(:a_method!)
|
107
|
+
end
|
108
|
+
|
109
|
+
trigger :check_other_user_response do
|
110
|
+
user.respond_to?(:a_method!)
|
111
|
+
end
|
112
|
+
|
113
|
+
module User
|
114
|
+
def a_method!; end
|
115
|
+
end
|
116
|
+
module OtherUser
|
117
|
+
def a_method!; end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe Surrounded::Context, 'assigning roles' do
|
122
|
+
let(:user){ CastingUser.new("Jim") }
|
123
|
+
let(:other_user){ User.new("Guille") }
|
124
|
+
let(:context){ RoleAssignmentContext.new(user, other_user) }
|
125
|
+
|
126
|
+
it 'tries to use casting to add roles' do
|
127
|
+
refute_includes(context.user_ancestors, RoleAssignmentContext::User)
|
128
|
+
end
|
129
|
+
|
130
|
+
it 'extends objects with role modules failing casting' do
|
131
|
+
assert_includes(context.other_user_ancestors, RoleAssignmentContext::OtherUser)
|
132
|
+
end
|
133
|
+
|
134
|
+
it 'sets role players to respond to role methods' do
|
135
|
+
assert context.check_user_response
|
136
|
+
assert context.check_other_user_response
|
137
|
+
end
|
138
|
+
|
139
|
+
it 'will not use classes as roles' do
|
140
|
+
ClassRoleAssignmentContext = Class.new do
|
141
|
+
extend Surrounded::Context
|
142
|
+
|
143
|
+
setup(:thing, :the_test)
|
144
|
+
|
145
|
+
trigger :check_user_response do
|
146
|
+
the_test.refute thing.respond_to?(:method_from_class), 'did respond to :method_from_class'
|
147
|
+
end
|
148
|
+
|
149
|
+
class Thing
|
150
|
+
def method_from_class; end
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
user = User.new('Jim')
|
156
|
+
|
157
|
+
context = ClassRoleAssignmentContext.new(user, self)
|
158
|
+
|
159
|
+
context.check_user_response
|
160
|
+
end
|
161
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
describe "Surrounded", 'without context' do
|
4
|
+
|
5
|
+
let(:jim){ User.new("Jim") }
|
6
|
+
|
7
|
+
it "never has context roles" do
|
8
|
+
Thread.current[:context] = []
|
9
|
+
assert_nil jim.send(:context).role?('anything')
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
describe "Surrounded" do
|
15
|
+
let(:jim){ User.new("Jim") }
|
16
|
+
let(:guille){ User.new("Guille") }
|
17
|
+
let(:external_user){ User.new("External User") }
|
18
|
+
|
19
|
+
let(:context){
|
20
|
+
TestContext.new(jim, guille)
|
21
|
+
}
|
22
|
+
|
23
|
+
before do
|
24
|
+
Thread.current[:context] = [context]
|
25
|
+
end
|
26
|
+
|
27
|
+
it "has access to objects in the context" do
|
28
|
+
assert_equal jim.other_user, guille
|
29
|
+
end
|
30
|
+
it "responds to messages for roles on the context" do
|
31
|
+
assert jim.respond_to?(:other_user)
|
32
|
+
|
33
|
+
Thread.current[:context] = []
|
34
|
+
|
35
|
+
refute jim.respond_to?(:other_user)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "prevents access to context objects for external objects" do
|
39
|
+
assert_raises(NoMethodError){
|
40
|
+
external_user.user
|
41
|
+
}
|
42
|
+
end
|
43
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'simplecov'
|
2
|
+
require 'minitest/autorun'
|
3
|
+
require 'coveralls'
|
4
|
+
|
5
|
+
if ENV['COVERALLS']
|
6
|
+
Coveralls.wear!
|
7
|
+
end
|
8
|
+
|
9
|
+
require 'surrounded'
|
10
|
+
require 'surrounded/context'
|
11
|
+
|
12
|
+
User = Struct.new(:name)
|
13
|
+
class User
|
14
|
+
include Surrounded
|
15
|
+
end
|
16
|
+
|
17
|
+
class TestContext
|
18
|
+
extend Surrounded::Context
|
19
|
+
|
20
|
+
setup(:user, :other_user)
|
21
|
+
|
22
|
+
trigger :access_other_object do
|
23
|
+
user.other_user.name
|
24
|
+
end
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: surrounded
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- '''Jim Gay'''
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-06-12 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: Gives an object implicit access to other objects in it's environment.
|
42
|
+
email:
|
43
|
+
- jim@saturnflyer.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- .gitignore
|
49
|
+
- .simplecov
|
50
|
+
- .travis.yml
|
51
|
+
- Gemfile
|
52
|
+
- LICENSE.txt
|
53
|
+
- README.md
|
54
|
+
- Rakefile
|
55
|
+
- examples/rails.rb
|
56
|
+
- lib/surrounded.rb
|
57
|
+
- lib/surrounded/context.rb
|
58
|
+
- lib/surrounded/version.rb
|
59
|
+
- surrounded.gemspec
|
60
|
+
- test/surrounded_context_test.rb
|
61
|
+
- test/surrounded_test.rb
|
62
|
+
- test/test_helper.rb
|
63
|
+
homepage: http://github.com/saturnflyer/surrounded
|
64
|
+
licenses:
|
65
|
+
- MIT
|
66
|
+
metadata: {}
|
67
|
+
post_install_message:
|
68
|
+
rdoc_options: []
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - '>='
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - '>='
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
requirements: []
|
82
|
+
rubyforge_project:
|
83
|
+
rubygems_version: 2.0.3
|
84
|
+
signing_key:
|
85
|
+
specification_version: 4
|
86
|
+
summary: Create encapsulated environments for your objects.
|
87
|
+
test_files:
|
88
|
+
- test/surrounded_context_test.rb
|
89
|
+
- test/surrounded_test.rb
|
90
|
+
- test/test_helper.rb
|