hooked 0.0.1 → 0.1.0
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.
- data/Gemfile +1 -3
- data/README.md +177 -8
- data/hooked.gemspec +4 -4
- data/lib/hooked/context.rb +39 -0
- data/lib/hooked/controller.rb +29 -41
- data/lib/hooked/hook.rb +2 -2
- data/lib/hooked/hookable.rb +3 -3
- data/lib/hooked/version.rb +1 -1
- data/lib/hooked.rb +1 -0
- data/test/container_test.rb +1 -1
- data/test/controller_test.rb +110 -52
- data/test/helper.rb +2 -1
- data/test/hook_test.rb +14 -19
- data/test/hookable_test.rb +14 -19
- metadata +8 -13
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,12 +1,181 @@
|
|
1
|
-
Ruby
|
1
|
+
Ruby Library For Aspect Oriented Programming
|
2
2
|
============================================
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
after and around.
|
4
|
+
Hooked makes AOP a breeze. It lets you define and invoke code that gems or other
|
5
|
+
parts of your application can hook onto.
|
7
6
|
|
8
|
-
|
9
|
-
|
7
|
+
Getting Started
|
8
|
+
---------------
|
10
9
|
|
11
|
-
|
12
|
-
|
10
|
+
require "hooked"
|
11
|
+
|
12
|
+
class User
|
13
|
+
include Hooked::Container
|
14
|
+
|
15
|
+
def save
|
16
|
+
hookable(:save_user, self)
|
17
|
+
end
|
18
|
+
|
19
|
+
hookable :save_user do |ctx|
|
20
|
+
ctx.return(Database.save(ctx.args))
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
class PermissionCheck
|
25
|
+
include Hooked::Container
|
26
|
+
|
27
|
+
def has_permissions?
|
28
|
+
false
|
29
|
+
end
|
30
|
+
|
31
|
+
before :save_user, :check_permissions do |ctx|
|
32
|
+
ctx.break unless has_permissions?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
user = User.new
|
37
|
+
hooking = Hooked::Controller.new(user, PermissionCheck.new)
|
38
|
+
user.save # => false
|
39
|
+
|
40
|
+
Defining Hookable Code
|
41
|
+
----------------------
|
42
|
+
|
43
|
+
To be able to define hookable code (as well as hooks) you need to mixin the
|
44
|
+
Hooked::Container module. Afterwards you can use the `::hookable` method which
|
45
|
+
takes a symbol as the hookable's name as well as a block.
|
46
|
+
|
47
|
+
class Something
|
48
|
+
include Hooked::Container
|
49
|
+
|
50
|
+
hookable :breakfast do |ctx|
|
51
|
+
puts "I'm having breakfast"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
The hookable will always be executed in the context of its container's object.
|
56
|
+
|
57
|
+
Defining Hooks
|
58
|
+
--------------
|
59
|
+
|
60
|
+
For defining hooks you can use the methods `::before`, `::after` and
|
61
|
+
`::around`. They are shortcuts to `::hook` and take the arguments
|
62
|
+
`hookable_name` (name of the hookable you want to hook onto), `hook_name` and
|
63
|
+
an optional hash of before/after options.
|
64
|
+
|
65
|
+
before :breakfast, :get_up do |ctx|
|
66
|
+
puts "I'm getting out of the bed"
|
67
|
+
end
|
68
|
+
|
69
|
+
before :breakfast, :make_coffee, :after => :get_up do |ctx|
|
70
|
+
puts "I'm making coffee"
|
71
|
+
end
|
72
|
+
|
73
|
+
`:before` and `:after` can be a single hook name, an array of hook names or
|
74
|
+
`:all`. More documentation on before/after relations and how they are being
|
75
|
+
resolved can be found in the
|
76
|
+
[depression gem](http://rubygems.org/gems/depression).
|
77
|
+
|
78
|
+
Hooks will always be executed in the context of their container's object (just
|
79
|
+
as hookables).
|
80
|
+
|
81
|
+
**Note:** Hooked will at no point guarantee that hooks will be executed in the
|
82
|
+
order of definition. If you rely on execution order, you should use the
|
83
|
+
`:before` and `:after` options.
|
84
|
+
|
85
|
+
Hooking Controller
|
86
|
+
------------------
|
87
|
+
|
88
|
+
The controller is responsible for bringing hooks into the desired order and
|
89
|
+
executing them correctly.
|
90
|
+
|
91
|
+
hooking = Hooked::Controller.new(container1, container2)
|
92
|
+
result = hooking.invoke(:my_hookable, arguments)
|
93
|
+
|
94
|
+
Arguments And Return Values
|
95
|
+
---------------------------
|
96
|
+
|
97
|
+
All argument and return value stuff (and control flow) is managed through a
|
98
|
+
context object that is being passed between the hooks as well as the hookable.
|
99
|
+
You can pass in and out whatever you want.
|
100
|
+
|
101
|
+
after(:lowercase, :confuse_programmer) do |ctx|
|
102
|
+
ctx.result = ctx.args.map {|s| s.upper }
|
103
|
+
end
|
104
|
+
|
105
|
+
res = hooking.invoke(:lowercase, ["asdf", :foo, 123]) do |ctx|
|
106
|
+
ctx.result = ctx.args.map {|s| s.to_s.lower }
|
107
|
+
end
|
108
|
+
|
109
|
+
p res # => ["ASDF", "FOO", "123"]
|
110
|
+
|
111
|
+
Control Flow
|
112
|
+
------------
|
113
|
+
|
114
|
+
If you want to cancel the execution of a hook and immediately start with the
|
115
|
+
next one, use `Context#next`. You would probably want to set a return value
|
116
|
+
first with `Context#result=`. `Context#return` basically does the same thing,
|
117
|
+
but additionally it always sets the return value to whatever you pass as an
|
118
|
+
argument.
|
119
|
+
|
120
|
+
# this
|
121
|
+
ctx.return "asd"
|
122
|
+
# is equal to
|
123
|
+
ctx.result = "asd"
|
124
|
+
ctx.next
|
125
|
+
|
126
|
+
# and this will result in ctx.result being nil after returning
|
127
|
+
ctx.result = 123
|
128
|
+
ctx.return
|
129
|
+
|
130
|
+
If you want to cancel the whole invocation you can use `Context#break`. This
|
131
|
+
will immediately return to where `Controller#invoke` was called. So if you call
|
132
|
+
it before the hookable was executed, it won't be executed at all.
|
133
|
+
|
134
|
+
hookable :foo do |ctx|
|
135
|
+
puts "You won't see me :/"
|
136
|
+
end
|
137
|
+
|
138
|
+
before :foo, :break_the_chain do |ctx|
|
139
|
+
ctx.break
|
140
|
+
end
|
141
|
+
|
142
|
+
You can pass a Fixnum to `Context#next` and `Context#break` to specify the
|
143
|
+
stack depth you want to jump. If you jump to high (e.g. you're hooking two
|
144
|
+
invocations deep but do `ctx.break 3`) a `NameError: uncaught throw 'hooked'`
|
145
|
+
will be raised.
|
146
|
+
|
147
|
+
**NOTE** You should definitely _not_ use Ruby's control flow constructs, they
|
148
|
+
may cause serious trouble that can be hard to debug. This may change in future
|
149
|
+
versions of Hooked. (I appreciate Pull Requests! :>)
|
150
|
+
|
151
|
+
Dependencies
|
152
|
+
------------
|
153
|
+
|
154
|
+
* [depression](https://rubygems.org/gems/depression)
|
155
|
+
* Ruby 1.9.2
|
156
|
+
* Possibly other versions and platforms, I didn't test any yet.
|
157
|
+
* [mocha](https://rubygems.org/gems/mocha) and
|
158
|
+
[test-unit](https://rubygems.org/gems/test-unit) for development
|
159
|
+
|
160
|
+
To do & Ideas
|
161
|
+
-------------
|
162
|
+
|
163
|
+
* Skipping hooks
|
164
|
+
* Visualization of hook flow
|
165
|
+
* Make backtraces more readable
|
166
|
+
* Make Ruby's control flow constructs work for hooks
|
167
|
+
|
168
|
+
Contributing
|
169
|
+
------------
|
170
|
+
|
171
|
+
1. Fork at [github.com/lgierth/hooked](https://github.com/lgierth/hooked)
|
172
|
+
2. Create a new branch
|
173
|
+
3. Commit, commit, commit!
|
174
|
+
4. Open a Pull Request
|
175
|
+
|
176
|
+
You can also open an issue for discussion first, if you like.
|
177
|
+
|
178
|
+
License
|
179
|
+
-------
|
180
|
+
|
181
|
+
Hooked is subject to an MIT-style license that can be found in the LICENSE file.
|
data/hooked.gemspec
CHANGED
@@ -10,10 +10,10 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.authors = ["Lars Gierth"]
|
11
11
|
s.email = ["lars.gierth@gmail.com"]
|
12
12
|
s.homepage = "http://rubygems.org/gems/hooked"
|
13
|
-
s.summary = %q{Ruby
|
14
|
-
s.description = %q{
|
15
|
-
code that
|
16
|
-
onto.
|
13
|
+
s.summary = %q{Ruby Library For Aspect Oriented Programming}
|
14
|
+
s.description = %q{Hooked makes AOP a breeze. It lets you define and invoke
|
15
|
+
code that gems or other parts of your application can hook
|
16
|
+
onto.}
|
17
17
|
|
18
18
|
s.add_dependency "depression"
|
19
19
|
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Hooked
|
2
|
+
class Context
|
3
|
+
attr_reader :args
|
4
|
+
attr_accessor :result
|
5
|
+
|
6
|
+
def initialize(args)
|
7
|
+
@args = args
|
8
|
+
end
|
9
|
+
|
10
|
+
def next(depth = 1)
|
11
|
+
return if depth < 1
|
12
|
+
throw(:hooked, {
|
13
|
+
:type => :next,
|
14
|
+
:depth => depth
|
15
|
+
})
|
16
|
+
end
|
17
|
+
|
18
|
+
def return(result)
|
19
|
+
@result = result
|
20
|
+
self.next
|
21
|
+
end
|
22
|
+
|
23
|
+
def break(depth = 1)
|
24
|
+
return if depth < 1
|
25
|
+
throw(:hooked, {
|
26
|
+
:type => :break,
|
27
|
+
:depth => depth
|
28
|
+
})
|
29
|
+
end
|
30
|
+
|
31
|
+
def skip(*hooks)
|
32
|
+
raise "Not yet implemented."
|
33
|
+
end
|
34
|
+
|
35
|
+
def skip_other(hookable, *hooks)
|
36
|
+
raise "Not yet implemented."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/hooked/controller.rb
CHANGED
@@ -2,11 +2,9 @@ module Hooked
|
|
2
2
|
Result = Struct.new(:type, :value)
|
3
3
|
|
4
4
|
class Controller
|
5
|
-
attr_reader :
|
5
|
+
attr_reader :containers, :hookables, :hooks
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
@name = name
|
9
|
-
|
7
|
+
def initialize(*containers)
|
10
8
|
@containers = []
|
11
9
|
@hookables = {}
|
12
10
|
@hooks = {}
|
@@ -18,6 +16,7 @@ module Hooked
|
|
18
16
|
def add(*containers)
|
19
17
|
@containers.push(*containers)
|
20
18
|
containers.each do |c|
|
19
|
+
c.hooked = self
|
21
20
|
c.class.hookables.each do |hkbl|
|
22
21
|
hkbl.container = c
|
23
22
|
hookables[hkbl.name] = hkbl
|
@@ -34,59 +33,48 @@ module Hooked
|
|
34
33
|
hooks.each {|hkbl_name, hs| hooks[hkbl_name] = Depression.process(hs) }
|
35
34
|
end
|
36
35
|
|
37
|
-
def invoke(name,
|
36
|
+
def invoke(name, args = nil, &block)
|
38
37
|
if block
|
39
38
|
hookable = Hookable.new(name, &block)
|
40
39
|
else
|
41
40
|
hookable = hookables[name] || raise("Unknown hookable: #{name}")
|
42
41
|
end
|
43
42
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end.call(args)
|
43
|
+
context = Context.new(args)
|
44
|
+
chain = (hooks[name] || []).reverse.inject(hookable) do |next_item, item|
|
45
|
+
proc {|ctx| call_hook(item, next_item, ctx) }
|
48
46
|
end
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
47
|
+
|
48
|
+
ctrl = catch(:hooked) { chain.call(context); nil }
|
49
|
+
|
50
|
+
if ctrl.respond_to?(:[])
|
51
|
+
ctrl[:depth] -= 1
|
52
|
+
throw(:hooked, ctrl) if ctrl[:depth] > 0
|
53
53
|
end
|
54
|
+
|
55
|
+
context.result
|
54
56
|
end
|
55
57
|
|
56
|
-
|
57
|
-
|
58
|
-
# def call_wrapped_hook(hook, hookable, args)
|
59
|
-
# ret = nil
|
60
|
-
#
|
61
|
-
# j = (0..1).each do |i|
|
62
|
-
# ret = call_hook(hook, hookable, args) if i == 0
|
63
|
-
# end
|
64
|
-
# unless j
|
65
|
-
# throw(:hooked, :hooked_abort_chain)
|
66
|
-
# end
|
67
|
-
#
|
68
|
-
# ret
|
69
|
-
# end
|
70
|
-
|
71
|
-
def call_hook(hook, hookable, args)
|
72
|
-
args = hook.call(*args) if hook.before?
|
58
|
+
def call_hook(hook, hookable, context)
|
59
|
+
wrap { hook.call(context) } if hook.before?
|
73
60
|
|
74
|
-
|
61
|
+
wrap do
|
75
62
|
if hook.around?
|
76
|
-
hook.call(
|
63
|
+
hook.call(context, hookable)
|
77
64
|
else
|
78
|
-
hookable.call(
|
65
|
+
hookable.call(context)
|
79
66
|
end
|
80
67
|
end
|
81
|
-
if result == :hooked_abort_chain
|
82
|
-
throw(:hooked, [:hooked_abort_chain, args])
|
83
|
-
else
|
84
|
-
args = result
|
85
|
-
end
|
86
|
-
|
87
|
-
args = hook.call(*args) if hook.after?
|
88
68
|
|
89
|
-
|
69
|
+
wrap { hook.call(context) } if hook.after?
|
70
|
+
end
|
71
|
+
|
72
|
+
def wrap(&block)
|
73
|
+
ctrl = catch(:hooked) { block.call; nil }
|
74
|
+
if ctrl.respond_to?(:[]) && (
|
75
|
+
ctrl[:type] == :break || ctrl[:depth] > 1)
|
76
|
+
throw(:hooked, ctrl)
|
77
|
+
end
|
90
78
|
end
|
91
79
|
end
|
92
80
|
end
|
data/lib/hooked/hook.rb
CHANGED
@@ -13,9 +13,9 @@ module Hooked
|
|
13
13
|
@container, @block = container, block
|
14
14
|
end
|
15
15
|
|
16
|
-
def call(
|
16
|
+
def call(context)
|
17
17
|
raise RuntimeError, "No container set for hook `#{name}'" unless container
|
18
|
-
container.instance_exec(
|
18
|
+
container.instance_exec(context, &@block)
|
19
19
|
end
|
20
20
|
|
21
21
|
def before?
|
data/lib/hooked/hookable.rb
CHANGED
@@ -9,11 +9,11 @@ module Hooked
|
|
9
9
|
@name, @container, @block = name.to_sym, container, block
|
10
10
|
end
|
11
11
|
|
12
|
-
def call(
|
12
|
+
def call(context)
|
13
13
|
if container
|
14
|
-
container.instance_exec(
|
14
|
+
container.instance_exec(context, &@block)
|
15
15
|
else
|
16
|
-
@block.call(
|
16
|
+
@block.call(context)
|
17
17
|
end
|
18
18
|
end
|
19
19
|
end
|
data/lib/hooked/version.rb
CHANGED
data/lib/hooked.rb
CHANGED
data/test/container_test.rb
CHANGED
data/test/controller_test.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require File.expand_path(
|
1
|
+
require File.expand_path("../helper", __FILE__)
|
2
2
|
|
3
3
|
class ControllerTest < Test::Unit::TestCase
|
4
4
|
def setup
|
@@ -20,15 +20,8 @@ class ControllerTest < Test::Unit::TestCase
|
|
20
20
|
].map {|c| c.new }
|
21
21
|
end
|
22
22
|
|
23
|
-
def test_has_a_name
|
24
|
-
name = :foo
|
25
|
-
controller = Hooked::Controller.new(name)
|
26
|
-
|
27
|
-
assert_equal name, controller.name
|
28
|
-
end
|
29
|
-
|
30
23
|
def test_has_containers
|
31
|
-
controller = Hooked::Controller.new(
|
24
|
+
controller = Hooked::Controller.new(@containers[0])
|
32
25
|
controller.add(*@containers[1..2])
|
33
26
|
|
34
27
|
assert_equal @containers, controller.containers
|
@@ -40,7 +33,7 @@ class ControllerTest < Test::Unit::TestCase
|
|
40
33
|
@containers[0].class.hooks[:foo][0],
|
41
34
|
@containers[1].class.hooks[:foo][0]
|
42
35
|
]
|
43
|
-
controller = Hooked::Controller.new(
|
36
|
+
controller = Hooked::Controller.new(*@containers)
|
44
37
|
|
45
38
|
assert_equal hookable, controller.hookables[hookable.name]
|
46
39
|
assert_equal hooks, controller.hooks[:foo]
|
@@ -57,10 +50,10 @@ class ControllerTest < Test::Unit::TestCase
|
|
57
50
|
end
|
58
51
|
order = [:do_laundry, :do_something_more, :do_something_else, :do_something, :do_dishes]
|
59
52
|
|
60
|
-
controller = Hooked::Controller.new(
|
53
|
+
controller = Hooked::Controller.new(container.new)
|
61
54
|
assert_equal order, controller.hooks[:foo].map {|h| h.name }
|
62
55
|
|
63
|
-
controller = Hooked::Controller.new
|
56
|
+
controller = Hooked::Controller.new
|
64
57
|
controller.add(container.new)
|
65
58
|
assert_not_equal order, controller.hooks[:foo].map {|h| h.name }
|
66
59
|
|
@@ -70,11 +63,18 @@ class ControllerTest < Test::Unit::TestCase
|
|
70
63
|
end
|
71
64
|
|
72
65
|
def test_sets_the_container_on_hookables_and_hooks
|
73
|
-
controller = Hooked::Controller.new(
|
66
|
+
controller = Hooked::Controller.new(*@containers[1..2])
|
74
67
|
assert_equal @containers[1], controller.hooks[:foo][0].container
|
75
68
|
assert_equal @containers[2], controller.hookables[:foo].container
|
76
69
|
end
|
77
70
|
|
71
|
+
def test_containers_are_aware_of_their_controller
|
72
|
+
controller = Hooked::Controller.new(*@containers)
|
73
|
+
@containers.each do |c|
|
74
|
+
assert_equal controller, c.hooked
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
78
|
def test_invokes_hooks_in_their_containers_scope
|
79
79
|
scope = nil
|
80
80
|
container = Class.new do
|
@@ -83,7 +83,7 @@ class ControllerTest < Test::Unit::TestCase
|
|
83
83
|
before(:something, :do_something_else) { scope = self }
|
84
84
|
end.new
|
85
85
|
|
86
|
-
controller = Hooked::Controller.new(
|
86
|
+
controller = Hooked::Controller.new(container)
|
87
87
|
controller.invoke(:something)
|
88
88
|
|
89
89
|
assert_equal(container, scope)
|
@@ -96,14 +96,14 @@ class ControllerTest < Test::Unit::TestCase
|
|
96
96
|
hookable(:something) { scope = self }
|
97
97
|
end.new
|
98
98
|
|
99
|
-
controller = Hooked::Controller.new(
|
99
|
+
controller = Hooked::Controller.new(container)
|
100
100
|
controller.invoke(:something)
|
101
101
|
|
102
102
|
assert_equal(container, scope)
|
103
103
|
end
|
104
104
|
|
105
105
|
def test_invokes_dynamic_hookables_in_their_original_scope
|
106
|
-
controller = Hooked::Controller.new
|
106
|
+
controller = Hooked::Controller.new
|
107
107
|
|
108
108
|
scope = nil
|
109
109
|
controller.invoke(:something) { scope = self }
|
@@ -111,66 +111,124 @@ class ControllerTest < Test::Unit::TestCase
|
|
111
111
|
assert_equal(self, scope)
|
112
112
|
end
|
113
113
|
|
114
|
-
def
|
115
|
-
|
114
|
+
def test_passes_arguments_into_the_context
|
115
|
+
input = "asd"
|
116
|
+
args = []
|
116
117
|
|
117
|
-
called = []
|
118
118
|
container = Class.new do
|
119
119
|
include(Hooked::Container)
|
120
|
-
|
121
|
-
|
122
|
-
before(:something, :do_something_else) { next; called << :do_something_else }
|
120
|
+
before(:something, :do_something_else) {|ctx| args << ctx.args}
|
121
|
+
hookable(:something) {|ctx| args << ctx.args }
|
123
122
|
end
|
123
|
+
|
124
|
+
controller = Hooked::Controller.new(container.new)
|
125
|
+
controller.invoke(:something, input)
|
124
126
|
|
125
|
-
|
126
|
-
controller.invoke(:something)
|
127
|
-
assert_equal [:do_something_more, :something], called
|
127
|
+
assert_equal [input.object_id, input.object_id], args.map {|a| a.object_id }
|
128
128
|
end
|
129
129
|
|
130
|
-
def
|
131
|
-
|
130
|
+
def test_returns_the_contexts_result
|
131
|
+
input = ["asd", "def"]
|
132
132
|
|
133
|
-
called = []
|
134
|
-
returned_arg = nil
|
135
133
|
container = Class.new do
|
136
134
|
include(Hooked::Container)
|
137
|
-
hookable(:something) {
|
138
|
-
|
139
|
-
before(:something, :do_something_else) {|arg| returned_arg = arg; return; called << :do_something_else }
|
135
|
+
hookable(:something) {|ctx| ctx.result = [input[0]] }
|
136
|
+
after(:something, :do_something_else) {|ctx| ctx.result << input[1] }
|
140
137
|
end
|
141
138
|
|
142
|
-
controller = Hooked::Controller.new(
|
143
|
-
|
144
|
-
|
145
|
-
assert_equal
|
146
|
-
assert_equal 3, returned_arg
|
139
|
+
controller = Hooked::Controller.new(container.new)
|
140
|
+
result = controller.invoke(:something)
|
141
|
+
|
142
|
+
assert_equal(input.map {|i| i.object_id }, result.map {|r| r.object_id })
|
147
143
|
end
|
148
144
|
|
149
|
-
def
|
150
|
-
|
145
|
+
def test_jumps_to_the_next_hook_or_hookable_when_it_catches_next
|
146
|
+
container = Class.new do
|
147
|
+
include(Hooked::Container)
|
148
|
+
before(:something, :asd, :before => :foo) {|ctx| ctx.result = [] }
|
149
|
+
before(:something, :foo) {|ctx| ctx.next; ctx.result << :foo }
|
150
|
+
hookable(:something) {|ctx| ctx.result << :something }
|
151
|
+
after(:something, :bar) {|ctx| ctx.result << :bar }
|
152
|
+
end
|
151
153
|
|
154
|
+
result = Hooked::Controller.new(container.new).invoke(:something)
|
155
|
+
assert_equal [:something, :bar], result
|
156
|
+
end
|
157
|
+
|
158
|
+
def test_next_can_jump_out_of_the_current_invocation
|
152
159
|
called = []
|
160
|
+
|
161
|
+
container1 = Class.new do
|
162
|
+
include(Hooked::Container)
|
163
|
+
hookable(:something) do |ctx|
|
164
|
+
hookable(:something_else)
|
165
|
+
called << :something
|
166
|
+
end
|
167
|
+
after(:something, :bar) {|ctx| called << :bar }
|
168
|
+
end
|
169
|
+
container2 = Class.new do
|
170
|
+
include(Hooked::Container)
|
171
|
+
before(:something_else, :foo_else) {|ctx| called << :foo_else }
|
172
|
+
hookable(:something_else) {|ctx| ctx.next(2); called << :something_else }
|
173
|
+
end
|
174
|
+
|
175
|
+
Hooked::Controller.new(container1.new, container2.new).invoke(:something)
|
176
|
+
assert_equal [:foo_else, :bar], called
|
177
|
+
end
|
178
|
+
|
179
|
+
def test_crashes_if_next_depth_is_bigger_than_invocation_depth
|
153
180
|
container = Class.new do
|
154
181
|
include(Hooked::Container)
|
155
|
-
hookable(:something) {
|
156
|
-
before(:something, :do_something_more) { called << :do_something_more }
|
157
|
-
before(:something, :do_something_else) { called << :do_something_else; break }
|
182
|
+
hookable(:something) {|ctx| ctx.next(2) }
|
158
183
|
end
|
159
184
|
|
160
|
-
|
161
|
-
|
162
|
-
|
185
|
+
assert_throw(:hooked) do
|
186
|
+
Hooked::Controller.new(container.new).invoke(:something)
|
187
|
+
end
|
163
188
|
end
|
164
189
|
|
165
|
-
def
|
190
|
+
def test_returns_from_the_current_invocation_when_it_catches_break
|
166
191
|
container = Class.new do
|
167
192
|
include(Hooked::Container)
|
168
|
-
|
169
|
-
before(:something, :
|
170
|
-
|
193
|
+
before(:something, :asd, :before => :foo) {|ctx| ctx.result = [] }
|
194
|
+
before(:something, :foo) {|ctx| ctx.result << :foo; ctx.break }
|
195
|
+
hookable(:something) {|ctx| ctx.result << :something }
|
196
|
+
after(:something, :bar) {|ctx| ctx.result << :bar }
|
197
|
+
end
|
198
|
+
|
199
|
+
result = Hooked::Controller.new(container.new).invoke(:something)
|
200
|
+
assert_equal [:foo], result
|
201
|
+
end
|
202
|
+
|
203
|
+
def test_break_can_jump_out_of_the_current_invocation
|
204
|
+
called = []
|
205
|
+
|
206
|
+
container1 = Class.new do
|
207
|
+
include(Hooked::Container)
|
208
|
+
hookable(:something) do |ctx|
|
209
|
+
hookable(:something_else)
|
210
|
+
called << :something
|
211
|
+
end
|
212
|
+
after(:something, :bar) {|ctx| called << :bar }
|
213
|
+
end
|
214
|
+
container2 = Class.new do
|
215
|
+
include(Hooked::Container)
|
216
|
+
before(:something_else, :foo_else) {|ctx| called << :foo_else }
|
217
|
+
hookable(:something_else) {|ctx| ctx.break(2); called << :something_else }
|
218
|
+
end
|
219
|
+
|
220
|
+
Hooked::Controller.new(container1.new, container2.new).invoke(:something)
|
221
|
+
assert_equal [:foo_else], called
|
222
|
+
end
|
223
|
+
|
224
|
+
def test_crashes_if_break_depth_is_bigger_than_invocation_depth
|
225
|
+
container = Class.new do
|
226
|
+
include(Hooked::Container)
|
227
|
+
hookable(:something) {|ctx| ctx.break(2) }
|
228
|
+
end
|
229
|
+
|
230
|
+
assert_throw(:hooked) do
|
231
|
+
Hooked::Controller.new(container.new).invoke(:something)
|
171
232
|
end
|
172
|
-
|
173
|
-
controller = Hooked::Controller.new(:foo, container.new)
|
174
|
-
assert_equal 4, controller.invoke(:something, 1)
|
175
233
|
end
|
176
234
|
end
|
data/test/helper.rb
CHANGED
data/test/hook_test.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path("../helper", __FILE__)
|
2
2
|
|
3
3
|
class HookTest < Test::Unit::TestCase
|
4
|
+
def context
|
5
|
+
Hooked::Context
|
6
|
+
end
|
7
|
+
|
4
8
|
def test_has_a_name
|
5
9
|
name = :foo
|
6
10
|
hook = Hooked::Hook.new(:before, name) {}
|
@@ -65,31 +69,22 @@ class HookTest < Test::Unit::TestCase
|
|
65
69
|
scope = self
|
66
70
|
end
|
67
71
|
|
68
|
-
assert_raise(RuntimeError) { hook.call }
|
72
|
+
assert_raise(RuntimeError) { hook.call(context) }
|
69
73
|
|
70
74
|
container = stub("container")
|
71
75
|
hook.container = container
|
72
|
-
hook.call
|
76
|
+
hook.call(context)
|
73
77
|
assert_equal(container, scope)
|
74
78
|
end
|
75
79
|
|
76
|
-
def
|
77
|
-
|
78
|
-
|
79
|
-
hook = Hooked::Hook.new(:before, :foo, {}, stub("container")) do
|
80
|
-
|
81
|
-
end
|
82
|
-
|
83
|
-
hook.call(args)
|
84
|
-
assert_equal(args, passed_args)
|
85
|
-
end
|
86
|
-
|
87
|
-
def test_passes_blocks_return_to_the_caller
|
88
|
-
ret = 123
|
89
|
-
hook = Hooked::Hook.new(:before, :foo, {}, stub("container")) do
|
90
|
-
ret
|
80
|
+
def test_passes_context_to_the_block
|
81
|
+
ctx = context
|
82
|
+
ctx2 = nil
|
83
|
+
hook = Hooked::Hook.new(:before, :foo, {}, stub("container")) do |c|
|
84
|
+
ctx2 = c
|
91
85
|
end
|
92
86
|
|
93
|
-
|
87
|
+
hook.call(ctx)
|
88
|
+
assert_equal(ctx, ctx2)
|
94
89
|
end
|
95
90
|
end
|
data/test/hookable_test.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
-
require File.
|
1
|
+
require File.expand_path("../helper", __FILE__)
|
2
2
|
|
3
3
|
class HookableTest < Test::Unit::TestCase
|
4
|
+
def context
|
5
|
+
Hooked::Context
|
6
|
+
end
|
7
|
+
|
4
8
|
def test_has_a_name
|
5
9
|
name = :foo
|
6
10
|
hookable = Hooked::Hookable.new(name) {}
|
@@ -35,7 +39,7 @@ class HookableTest < Test::Unit::TestCase
|
|
35
39
|
scope = self
|
36
40
|
end
|
37
41
|
|
38
|
-
hookable.call
|
42
|
+
hookable.call(context)
|
39
43
|
assert_equal container, scope
|
40
44
|
end
|
41
45
|
|
@@ -45,27 +49,18 @@ class HookableTest < Test::Unit::TestCase
|
|
45
49
|
scope = self
|
46
50
|
end
|
47
51
|
|
48
|
-
hookable.call
|
52
|
+
hookable.call(context)
|
49
53
|
assert_equal self, scope
|
50
54
|
end
|
51
55
|
|
52
|
-
def
|
53
|
-
|
54
|
-
|
55
|
-
hookable = Hooked::Hookable.new(:foo) do
|
56
|
-
|
57
|
-
end
|
58
|
-
|
59
|
-
hookable.call(args)
|
60
|
-
assert_equal(args, passed_args)
|
61
|
-
end
|
62
|
-
|
63
|
-
def test_passes_block_return_to_the_caller
|
64
|
-
ret = 123
|
65
|
-
hookable = Hooked::Hookable.new(:foo) do
|
66
|
-
ret
|
56
|
+
def test_passes_context_to_the_block
|
57
|
+
ctx = context
|
58
|
+
ctx2 = nil
|
59
|
+
hookable = Hooked::Hookable.new(:foo) do |c|
|
60
|
+
ctx2 = c
|
67
61
|
end
|
68
62
|
|
69
|
-
|
63
|
+
hookable.call(ctx)
|
64
|
+
assert_equal(ctx, ctx2)
|
70
65
|
end
|
71
66
|
end
|
metadata
CHANGED
@@ -1,13 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hooked
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 29
|
5
4
|
prerelease: false
|
6
5
|
segments:
|
7
6
|
- 0
|
8
|
-
- 0
|
9
7
|
- 1
|
10
|
-
|
8
|
+
- 0
|
9
|
+
version: 0.1.0
|
11
10
|
platform: ruby
|
12
11
|
authors:
|
13
12
|
- Lars Gierth
|
@@ -15,7 +14,7 @@ autorequire:
|
|
15
14
|
bindir: bin
|
16
15
|
cert_chain: []
|
17
16
|
|
18
|
-
date: 2010-
|
17
|
+
date: 2010-12-20 00:00:00 +01:00
|
19
18
|
default_executable:
|
20
19
|
dependencies:
|
21
20
|
- !ruby/object:Gem::Dependency
|
@@ -26,7 +25,6 @@ dependencies:
|
|
26
25
|
requirements:
|
27
26
|
- - ">="
|
28
27
|
- !ruby/object:Gem::Version
|
29
|
-
hash: 3
|
30
28
|
segments:
|
31
29
|
- 0
|
32
30
|
version: "0"
|
@@ -40,7 +38,6 @@ dependencies:
|
|
40
38
|
requirements:
|
41
39
|
- - ">="
|
42
40
|
- !ruby/object:Gem::Version
|
43
|
-
hash: 3
|
44
41
|
segments:
|
45
42
|
- 0
|
46
43
|
version: "0"
|
@@ -54,16 +51,15 @@ dependencies:
|
|
54
51
|
requirements:
|
55
52
|
- - ">="
|
56
53
|
- !ruby/object:Gem::Version
|
57
|
-
hash: 3
|
58
54
|
segments:
|
59
55
|
- 0
|
60
56
|
version: "0"
|
61
57
|
type: :development
|
62
58
|
version_requirements: *id003
|
63
59
|
description: |-
|
64
|
-
|
65
|
-
code that
|
66
|
-
onto.
|
60
|
+
Hooked makes AOP a breeze. It lets you define and invoke
|
61
|
+
code that gems or other parts of your application can hook
|
62
|
+
onto.
|
67
63
|
email:
|
68
64
|
- lars.gierth@gmail.com
|
69
65
|
executables: []
|
@@ -80,6 +76,7 @@ files:
|
|
80
76
|
- hooked.gemspec
|
81
77
|
- lib/hooked.rb
|
82
78
|
- lib/hooked/container.rb
|
79
|
+
- lib/hooked/context.rb
|
83
80
|
- lib/hooked/controller.rb
|
84
81
|
- lib/hooked/hook.rb
|
85
82
|
- lib/hooked/hookable.rb
|
@@ -103,7 +100,6 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
103
100
|
requirements:
|
104
101
|
- - ">="
|
105
102
|
- !ruby/object:Gem::Version
|
106
|
-
hash: 3
|
107
103
|
segments:
|
108
104
|
- 0
|
109
105
|
version: "0"
|
@@ -112,7 +108,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
112
108
|
requirements:
|
113
109
|
- - ">="
|
114
110
|
- !ruby/object:Gem::Version
|
115
|
-
hash: 3
|
116
111
|
segments:
|
117
112
|
- 0
|
118
113
|
version: "0"
|
@@ -122,6 +117,6 @@ rubyforge_project:
|
|
122
117
|
rubygems_version: 1.3.7
|
123
118
|
signing_key:
|
124
119
|
specification_version: 3
|
125
|
-
summary: Ruby
|
120
|
+
summary: Ruby Library For Aspect Oriented Programming
|
126
121
|
test_files: []
|
127
122
|
|