hooked 0.0.1 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|