extlib 0.9.2
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of extlib might be problematic. Click here for more details.
- data/README +0 -0
- data/lib/extlib.rb +19 -0
- data/lib/extlib/assertions.rb +8 -0
- data/lib/extlib/blank.rb +42 -0
- data/lib/extlib/hook.rb +313 -0
- data/lib/extlib/inflection.rb +141 -0
- data/lib/extlib/lazy_array.rb +104 -0
- data/lib/extlib/module.rb +19 -0
- data/lib/extlib/object.rb +7 -0
- data/lib/extlib/pathname.rb +5 -0
- data/lib/extlib/pooling.rb +227 -0
- data/lib/extlib/string.rb +45 -0
- data/lib/extlib/struct.rb +8 -0
- data/spec/blank_spec.rb +85 -0
- data/spec/hook_spec.rb +897 -0
- data/spec/inflection_spec.rb +50 -0
- data/spec/lazy_array_spec.rb +882 -0
- data/spec/module_spec.rb +36 -0
- data/spec/object_spec.rb +4 -0
- data/spec/pooling_spec.rb +490 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/string_spec.rb +4 -0
- data/spec/struct_spec.rb +12 -0
- metadata +92 -0
data/spec/hook_spec.rb
ADDED
@@ -0,0 +1,897 @@
|
|
1
|
+
require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
|
2
|
+
|
3
|
+
describe Extlib::Hook do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
@module = Module.new do
|
7
|
+
def greet; greetings_from_module; end;
|
8
|
+
end
|
9
|
+
|
10
|
+
@class = Class.new do
|
11
|
+
include Extlib::Hook
|
12
|
+
|
13
|
+
def hookable; end;
|
14
|
+
def self.clakable; end;
|
15
|
+
def ambiguous; hi_mom!; end;
|
16
|
+
def self.ambiguous; hi_dad!; end;
|
17
|
+
end
|
18
|
+
|
19
|
+
@another_class = Class.new do
|
20
|
+
include Extlib::Hook
|
21
|
+
end
|
22
|
+
|
23
|
+
@other = Class.new do
|
24
|
+
include Extlib::Hook
|
25
|
+
|
26
|
+
def hookable; end
|
27
|
+
def self.clakable; end;
|
28
|
+
end
|
29
|
+
|
30
|
+
@class.register_instance_hooks :hookable
|
31
|
+
@class.register_class_hooks :clakable
|
32
|
+
end
|
33
|
+
|
34
|
+
#
|
35
|
+
# Specs out how hookable methods are registered
|
36
|
+
#
|
37
|
+
describe "explicit hookable method registration" do
|
38
|
+
|
39
|
+
describe "for class methods" do
|
40
|
+
|
41
|
+
it "shouldn't confuse instance method hooks and class method hooks" do
|
42
|
+
@class.register_instance_hooks :ambiguous
|
43
|
+
@class.register_class_hooks :ambiguous
|
44
|
+
|
45
|
+
@class.should_receive(:hi_dad!)
|
46
|
+
@class.ambiguous
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should be able to register multiple hookable methods at once" do
|
50
|
+
%w(method_one method_two method_three).each do |method|
|
51
|
+
@another_class.class_eval %(def self.#{method}; end;)
|
52
|
+
end
|
53
|
+
|
54
|
+
@another_class.register_class_hooks :method_one, :method_two, :method_three
|
55
|
+
@another_class.class_hooks.keys.should include(:method_one)
|
56
|
+
@another_class.class_hooks.keys.should include(:method_two)
|
57
|
+
@another_class.class_hooks.keys.should include(:method_three)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should not allow a method that does not exist to be registered as hookable" do
|
61
|
+
lambda { @another_class.register_class_hooks :method_one }.should raise_error(ArgumentError)
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should allow hooks to be registered on methods from module extensions" do
|
65
|
+
@class.extend(@module)
|
66
|
+
@class.register_class_hooks :greet
|
67
|
+
@class.class_hooks[:greet].should_not be_nil
|
68
|
+
end
|
69
|
+
|
70
|
+
it "should allow modules to register hooks in the self.extended method" do
|
71
|
+
@module.class_eval do
|
72
|
+
def self.extended(base)
|
73
|
+
base.register_class_hooks :greet
|
74
|
+
end
|
75
|
+
end
|
76
|
+
@class.extend(@module)
|
77
|
+
@class.class_hooks[:greet].should_not be_nil
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should be able to register protected methods as hooks" do
|
81
|
+
@class.class_eval %{protected; def self.protected_hookable; end;}
|
82
|
+
lambda { @class.register_class_hooks(:protected_hookable) }.should_not raise_error(ArgumentError)
|
83
|
+
end
|
84
|
+
|
85
|
+
it "should not be able to register private methods as hooks" do
|
86
|
+
@class.class_eval %{class << self; private; def private_hookable; end; end;}
|
87
|
+
lambda { @class.register_class_hooks(:private_hookable) }.should raise_error(ArgumentError)
|
88
|
+
end
|
89
|
+
|
90
|
+
it "should allow advising methods ending in ? or !" do
|
91
|
+
@class.class_eval do
|
92
|
+
def self.hookable!; two!; end;
|
93
|
+
def self.hookable?; three!; end;
|
94
|
+
register_class_hooks :hookable!, :hookable?
|
95
|
+
end
|
96
|
+
@class.before_class_method(:hookable!) { one! }
|
97
|
+
@class.after_class_method(:hookable?) { four! }
|
98
|
+
|
99
|
+
@class.should_receive(:one!).once.ordered
|
100
|
+
@class.should_receive(:two!).once.ordered
|
101
|
+
@class.should_receive(:three!).once.ordered
|
102
|
+
@class.should_receive(:four!).once.ordered
|
103
|
+
|
104
|
+
@class.hookable!
|
105
|
+
@class.hookable?
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should allow hooking methods ending in ?, ! or = with method hooks" do
|
109
|
+
@class.class_eval do
|
110
|
+
def self.before_hookable!; one!; end;
|
111
|
+
def self.hookable!; two!; end;
|
112
|
+
def self.hookable?; three!; end;
|
113
|
+
def self.after_hookable?; four!; end;
|
114
|
+
register_class_hooks :hookable!, :hookable?
|
115
|
+
end
|
116
|
+
@class.before_class_method(:hookable!, :before_hookable!)
|
117
|
+
@class.after_class_method(:hookable?, :after_hookable?)
|
118
|
+
|
119
|
+
@class.should_receive(:one!).once.ordered
|
120
|
+
@class.should_receive(:two!).once.ordered
|
121
|
+
@class.should_receive(:three!).once.ordered
|
122
|
+
@class.should_receive(:four!).once.ordered
|
123
|
+
|
124
|
+
@class.hookable!
|
125
|
+
@class.hookable?
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
describe "for instance methods" do
|
130
|
+
|
131
|
+
it "shouldn't confuse instance method hooks and class method hooks" do
|
132
|
+
@class.register_instance_hooks :ambiguous
|
133
|
+
@class.register_class_hooks :ambiguous
|
134
|
+
|
135
|
+
inst = @class.new
|
136
|
+
inst.should_receive(:hi_mom!)
|
137
|
+
inst.ambiguous
|
138
|
+
end
|
139
|
+
|
140
|
+
it "should be able to register multiple hookable methods at once" do
|
141
|
+
%w(method_one method_two method_three).each do |method|
|
142
|
+
@another_class.send(:define_method, method) {}
|
143
|
+
end
|
144
|
+
|
145
|
+
@another_class.register_instance_hooks :method_one, :method_two, :method_three
|
146
|
+
@another_class.instance_hooks.keys.should include(:method_one)
|
147
|
+
@another_class.instance_hooks.keys.should include(:method_two)
|
148
|
+
@another_class.instance_hooks.keys.should include(:method_three)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "should not allow a method that does not exist to be registered as hookable" do
|
152
|
+
lambda { @another_class.register_instance_hooks :method_one }.should raise_error(ArgumentError)
|
153
|
+
end
|
154
|
+
|
155
|
+
it "should allow hooks to be registered on included module methods" do
|
156
|
+
@class.send(:include, @module)
|
157
|
+
@class.register_instance_hooks :greet
|
158
|
+
@class.instance_hooks[:greet].should_not be_nil
|
159
|
+
end
|
160
|
+
|
161
|
+
it "should allow modules to register hooks in the self.included method" do
|
162
|
+
@module.class_eval do
|
163
|
+
def self.included(base)
|
164
|
+
base.register_instance_hooks :greet
|
165
|
+
end
|
166
|
+
end
|
167
|
+
@class.send(:include, @module)
|
168
|
+
@class.instance_hooks[:greet].should_not be_nil
|
169
|
+
end
|
170
|
+
|
171
|
+
it "should be able to register protected methods as hooks" do
|
172
|
+
@class.class_eval %{protected; def protected_hookable; end;}
|
173
|
+
lambda { @class.register_instance_hooks(:protected_hookable) }.should_not raise_error(ArgumentError)
|
174
|
+
end
|
175
|
+
|
176
|
+
it "should not be able to register private methods as hooks" do
|
177
|
+
@class.class_eval %{private; def private_hookable; end;}
|
178
|
+
lambda { @class.register_instance_hooks(:private_hookable) }.should raise_error(ArgumentError)
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should allow hooking methods ending in ? or ! with block hooks" do
|
182
|
+
@class.class_eval do
|
183
|
+
def hookable!; two!; end;
|
184
|
+
def hookable?; three!; end;
|
185
|
+
register_instance_hooks :hookable!, :hookable?
|
186
|
+
end
|
187
|
+
@class.before(:hookable!) { one! }
|
188
|
+
@class.after(:hookable?) { four! }
|
189
|
+
|
190
|
+
inst = @class.new
|
191
|
+
inst.should_receive(:one!).once.ordered
|
192
|
+
inst.should_receive(:two!).once.ordered
|
193
|
+
inst.should_receive(:three!).once.ordered
|
194
|
+
inst.should_receive(:four!).once.ordered
|
195
|
+
|
196
|
+
inst.hookable!
|
197
|
+
inst.hookable?
|
198
|
+
end
|
199
|
+
|
200
|
+
it "should allow hooking methods ending in ?, ! or = with method hooks" do
|
201
|
+
@class.class_eval do
|
202
|
+
def before_hookable(val); one!; end;
|
203
|
+
def hookable=(val); two!; end;
|
204
|
+
def hookable?; three!; end;
|
205
|
+
def after_hookable?; four!; end;
|
206
|
+
register_instance_hooks :hookable=, :hookable?
|
207
|
+
end
|
208
|
+
@class.before(:hookable=, :before_hookable)
|
209
|
+
@class.after(:hookable?, :after_hookable?)
|
210
|
+
|
211
|
+
inst = @class.new
|
212
|
+
inst.should_receive(:one!).once.ordered
|
213
|
+
inst.should_receive(:two!).once.ordered
|
214
|
+
inst.should_receive(:three!).once.ordered
|
215
|
+
inst.should_receive(:four!).once.ordered
|
216
|
+
|
217
|
+
inst.hookable = 'hello'
|
218
|
+
inst.hookable?
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
|
224
|
+
describe "implicit hookable method registration" do
|
225
|
+
|
226
|
+
describe "for class methods" do
|
227
|
+
it "should implicitly register the method as hookable" do
|
228
|
+
@class.class_eval %{def self.implicit_hook; end;}
|
229
|
+
@class.before_class_method(:implicit_hook) { hello }
|
230
|
+
|
231
|
+
@class.should_receive(:hello)
|
232
|
+
@class.implicit_hook
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
describe "for instance methods" do
|
237
|
+
it "should implicitly register the method as hookable" do
|
238
|
+
@class.class_eval %{def implicit_hook; end;}
|
239
|
+
@class.before(:implicit_hook) { hello }
|
240
|
+
|
241
|
+
inst = @class.new
|
242
|
+
inst.should_receive(:hello)
|
243
|
+
inst.implicit_hook
|
244
|
+
end
|
245
|
+
|
246
|
+
it 'should not overwrite methods included by modules after the hook is declared' do
|
247
|
+
my_module = Module.new do
|
248
|
+
# Just another module
|
249
|
+
@another_module = Module.new do
|
250
|
+
def some_method; "Hello " + super; end;
|
251
|
+
end
|
252
|
+
|
253
|
+
def some_method; "world"; end;
|
254
|
+
|
255
|
+
def self.included(base)
|
256
|
+
base.before(:some_method, :a_method)
|
257
|
+
base.send(:include, @another_module)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
@class.class_eval { include my_module }
|
262
|
+
|
263
|
+
inst = @class.new
|
264
|
+
inst.should_receive(:a_method)
|
265
|
+
inst.some_method.should == "Hello world"
|
266
|
+
end
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
270
|
+
|
271
|
+
describe "hook method registration" do
|
272
|
+
|
273
|
+
describe "for class methods" do
|
274
|
+
it "should complain when only one argument is passed" do
|
275
|
+
lambda { @class.before_class_method(:clakable) }.should raise_error(ArgumentError)
|
276
|
+
lambda { @class.after_class_method(:clakable) }.should raise_error(ArgumentError)
|
277
|
+
end
|
278
|
+
|
279
|
+
it "should complain when target_method is not a symbol" do
|
280
|
+
lambda { @class.before_class_method("clakable", :ambiguous) }.should raise_error(ArgumentError)
|
281
|
+
lambda { @class.after_class_method("clakable", :ambiguous) }.should raise_error(ArgumentError)
|
282
|
+
end
|
283
|
+
|
284
|
+
it "should complain when method_sym is not a symbol" do
|
285
|
+
lambda { @class.before_class_method(:clakable, "ambiguous") }.should raise_error(ArgumentError)
|
286
|
+
lambda { @class.after_class_method(:clakable, "ambiguous") }.should raise_error(ArgumentError)
|
287
|
+
end
|
288
|
+
|
289
|
+
it "should not allow methods ending in = to be hooks" do
|
290
|
+
lambda { @class.before_class_method(:clakable, :annoying=) }.should raise_error(ArgumentError)
|
291
|
+
lambda { @class.after_class_method(:clakable, :annoying=) }.should raise_error(ArgumentError)
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
describe "for instance methods" do
|
296
|
+
it "should complain when only one argument is passed" do
|
297
|
+
lambda { @class.before(:hookable) }.should raise_error(ArgumentError)
|
298
|
+
lambda { @class.after(:hookable) }.should raise_error(ArgumentError)
|
299
|
+
end
|
300
|
+
|
301
|
+
it "should complain when target_method is not a symbol" do
|
302
|
+
lambda { @class.before("hookable", :ambiguous) }.should raise_error(ArgumentError)
|
303
|
+
lambda { @class.after("hookable", :ambiguous) }.should raise_error(ArgumentError)
|
304
|
+
end
|
305
|
+
|
306
|
+
it "should complain when method_sym is not a symbol" do
|
307
|
+
lambda { @class.before(:hookable, "ambiguous") }.should raise_error(ArgumentError)
|
308
|
+
lambda { @class.after(:hookable, "ambiguous") }.should raise_error(ArgumentError)
|
309
|
+
end
|
310
|
+
|
311
|
+
it "should not allow methods ending in = to be hooks" do
|
312
|
+
lambda { @class.before(:hookable, :annoying=) }.should raise_error(ArgumentError)
|
313
|
+
lambda { @class.after(:hookable, :annoying=) }.should raise_error(ArgumentError)
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
end
|
318
|
+
|
319
|
+
#
|
320
|
+
# Specs out how hook methods / blocks are invoked when there is no inheritance
|
321
|
+
# involved
|
322
|
+
#
|
323
|
+
describe "hook invocation without inheritance" do
|
324
|
+
|
325
|
+
describe "for class methods" do
|
326
|
+
it 'should run an advice block' do
|
327
|
+
@class.before_class_method(:clakable) { hi_mom! }
|
328
|
+
@class.should_receive(:hi_mom!)
|
329
|
+
@class.clakable
|
330
|
+
end
|
331
|
+
|
332
|
+
it 'should run an advice method' do
|
333
|
+
@class.class_eval %{def self.before_method; hi_mom!; end;}
|
334
|
+
@class.before_class_method(:clakable, :before_method)
|
335
|
+
|
336
|
+
@class.should_receive(:hi_mom!)
|
337
|
+
@class.clakable
|
338
|
+
end
|
339
|
+
|
340
|
+
it 'should pass the hookable method arguments to the hook method' do
|
341
|
+
@class.class_eval %{def self.hook_this(word, lol); end;}
|
342
|
+
@class.register_class_hooks(:hook_this)
|
343
|
+
@class.before_class_method(:hook_this, :before_hook_this)
|
344
|
+
|
345
|
+
@class.should_receive(:before_hook_this).with("omg", "hi2u")
|
346
|
+
@class.hook_this("omg", "hi2u")
|
347
|
+
end
|
348
|
+
|
349
|
+
it 'should work with glob arguments (or whatever you call em)' do
|
350
|
+
@class.class_eval %{def self.hook_this(*args); end;}
|
351
|
+
@class.register_class_hooks(:hook_this)
|
352
|
+
@class.before_class_method(:hook_this, :before_hook_this)
|
353
|
+
|
354
|
+
@class.should_receive(:before_hook_this).with("omg", "hi2u", "lolercoaster")
|
355
|
+
@class.hook_this("omg", "hi2u", "lolercoaster")
|
356
|
+
end
|
357
|
+
|
358
|
+
it 'should allow the use of before and after together' do
|
359
|
+
@class.class_eval %{def self.before_hook; first!; end;}
|
360
|
+
@class.before_class_method(:clakable, :before_hook)
|
361
|
+
@class.after_class_method(:clakable) { last! }
|
362
|
+
|
363
|
+
@class.should_receive(:first!).once.ordered
|
364
|
+
@class.should_receive(:last!).once.ordered
|
365
|
+
@class.clakable
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
describe "for instance methods" do
|
370
|
+
it 'should run an advice block' do
|
371
|
+
@class.before(:hookable) { hi_mom! }
|
372
|
+
|
373
|
+
inst = @class.new
|
374
|
+
inst.should_receive(:hi_mom!)
|
375
|
+
inst.hookable
|
376
|
+
end
|
377
|
+
|
378
|
+
it 'should run an advice method' do
|
379
|
+
@class.send(:define_method, :before_method) { hi_mom! }
|
380
|
+
@class.before(:hookable, :before_method)
|
381
|
+
|
382
|
+
inst = @class.new
|
383
|
+
inst.should_receive(:hi_mom!)
|
384
|
+
inst.hookable
|
385
|
+
end
|
386
|
+
|
387
|
+
it 'should pass the hookable method arguments to the hook method' do
|
388
|
+
@class.class_eval %{def hook_this(word, lol); end;}
|
389
|
+
@class.register_instance_hooks(:hook_this)
|
390
|
+
@class.before(:hook_this, :before_hook_this)
|
391
|
+
|
392
|
+
inst = @class.new
|
393
|
+
inst.should_receive(:before_hook_this).with("omg", "hi2u")
|
394
|
+
inst.hook_this("omg", "hi2u")
|
395
|
+
end
|
396
|
+
|
397
|
+
it 'should work with glob arguments (or whatever you call em)' do
|
398
|
+
@class.class_eval %{def hook_this(*args); end;}
|
399
|
+
@class.register_instance_hooks(:hook_this)
|
400
|
+
@class.before(:hook_this, :before_hook_this)
|
401
|
+
|
402
|
+
inst = @class.new
|
403
|
+
inst.should_receive(:before_hook_this).with("omg", "hi2u", "lolercoaster")
|
404
|
+
inst.hook_this("omg", "hi2u", "lolercoaster")
|
405
|
+
end
|
406
|
+
|
407
|
+
it 'should allow the use of before and after together' do
|
408
|
+
@class.class_eval %{def after_hook; last!; end;}
|
409
|
+
@class.before(:hookable) { first! }
|
410
|
+
@class.after(:hookable, :after_hook)
|
411
|
+
|
412
|
+
inst = @class.new
|
413
|
+
inst.should_receive(:first!).once.ordered
|
414
|
+
inst.should_receive(:last!).once.ordered
|
415
|
+
inst.hookable
|
416
|
+
end
|
417
|
+
|
418
|
+
it 'should be able to use private methods as hooks' do
|
419
|
+
@class.class_eval %{private; def nike; doit!; end;}
|
420
|
+
@class.before(:hookable, :nike)
|
421
|
+
|
422
|
+
inst = @class.new
|
423
|
+
inst.should_receive(:doit!)
|
424
|
+
inst.hookable
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
end
|
429
|
+
|
430
|
+
describe "hook invocation with class inheritance" do
|
431
|
+
|
432
|
+
describe "for class methods" do
|
433
|
+
it 'should run an advice block when the class is inherited' do
|
434
|
+
@class.before_class_method(:clakable) { hi_mom! }
|
435
|
+
@child = Class.new(@class)
|
436
|
+
@child.should_receive(:hi_mom!)
|
437
|
+
@child.clakable
|
438
|
+
end
|
439
|
+
|
440
|
+
it 'should run an advice block on child class when hook is registered in parent after inheritance' do
|
441
|
+
@child = Class.new(@class)
|
442
|
+
@class.before_class_method(:clakable) { hi_mom! }
|
443
|
+
@child.should_receive(:hi_mom!)
|
444
|
+
@child.clakable
|
445
|
+
end
|
446
|
+
|
447
|
+
it 'should be able to declare advice methods in child classes' do
|
448
|
+
@class.class_eval %{def self.before_method; hi_dad!; end;}
|
449
|
+
@class.before_class_method(:clakable, :before_method)
|
450
|
+
|
451
|
+
@child = Class.new(@class) do
|
452
|
+
def self.child; hi_mom!; end;
|
453
|
+
before_class_method(:clakable, :child)
|
454
|
+
end
|
455
|
+
|
456
|
+
@child.should_receive(:hi_dad!).once.ordered
|
457
|
+
@child.should_receive(:hi_mom!).once.ordered
|
458
|
+
@child.clakable
|
459
|
+
end
|
460
|
+
|
461
|
+
it "should not execute hooks added in the child classes when in the parent class" do
|
462
|
+
@child = Class.new(@class) { def self.child; hi_mom!; end; }
|
463
|
+
@child.before_class_method(:clakable, :child)
|
464
|
+
@class.should_not_receive(:hi_mom!)
|
465
|
+
@class.clakable
|
466
|
+
end
|
467
|
+
|
468
|
+
it 'should not call the hook stack if the hookable method is overwritten and does not call super' do
|
469
|
+
@class.before_class_method(:clakable) { hi_mom! }
|
470
|
+
@child = Class.new(@class) do
|
471
|
+
def self.clakable; end;
|
472
|
+
end
|
473
|
+
|
474
|
+
@child.should_not_receive(:hi_mom!)
|
475
|
+
@child.clakable
|
476
|
+
end
|
477
|
+
|
478
|
+
it 'should not call hooks defined in the child class for a hookable method in a parent if the child overwrites the hookable method without calling super' do
|
479
|
+
@child = Class.new(@class) do
|
480
|
+
before_class_method(:clakable) { hi_mom! }
|
481
|
+
def self.clakable; end;
|
482
|
+
end
|
483
|
+
|
484
|
+
@child.should_not_receive(:hi_mom!)
|
485
|
+
@child.clakable
|
486
|
+
end
|
487
|
+
|
488
|
+
it 'should not call hooks defined in child class even if hook method exists in parent' do
|
489
|
+
@class.class_eval %{def self.hello_world; hello_world!; end;}
|
490
|
+
@child = Class.new(@class) do
|
491
|
+
before_class_method(:clakable, :hello_world)
|
492
|
+
end
|
493
|
+
|
494
|
+
@class.should_not_receive(:hello_world!)
|
495
|
+
@class.clakable
|
496
|
+
end
|
497
|
+
end
|
498
|
+
|
499
|
+
describe "for instance methods" do
|
500
|
+
it 'should run an advice block when the class is inherited' do
|
501
|
+
@inherited_class = Class.new(@class)
|
502
|
+
@class.before(:hookable) { hi_dad! }
|
503
|
+
|
504
|
+
inst = @inherited_class.new
|
505
|
+
inst.should_receive(:hi_dad!)
|
506
|
+
inst.hookable
|
507
|
+
end
|
508
|
+
|
509
|
+
it 'should run an advice block on child class when hook is registered in parent after inheritance' do
|
510
|
+
@child = Class.new(@class)
|
511
|
+
@class.before(:hookable) { hi_mom! }
|
512
|
+
|
513
|
+
inst = @child.new
|
514
|
+
inst.should_receive(:hi_mom!)
|
515
|
+
inst.hookable
|
516
|
+
end
|
517
|
+
|
518
|
+
it 'should be able to declare advice methods in child classes' do
|
519
|
+
@class.send(:define_method, :before_method) { hi_dad! }
|
520
|
+
@class.before(:hookable, :before_method)
|
521
|
+
|
522
|
+
@child = Class.new(@class) do
|
523
|
+
def child; hi_mom!; end;
|
524
|
+
before :hookable, :child
|
525
|
+
end
|
526
|
+
|
527
|
+
inst = @child.new
|
528
|
+
inst.should_receive(:hi_dad!).once.ordered
|
529
|
+
inst.should_receive(:hi_mom!).once.ordered
|
530
|
+
inst.hookable
|
531
|
+
end
|
532
|
+
|
533
|
+
it "should not execute hooks added in the child classes when in parent class" do
|
534
|
+
@child = Class.new(@class)
|
535
|
+
@child.send(:define_method, :child) { hi_mom! }
|
536
|
+
@child.before(:hookable, :child)
|
537
|
+
|
538
|
+
inst = @class.new
|
539
|
+
inst.should_not_receive(:hi_mom!)
|
540
|
+
inst.hookable
|
541
|
+
end
|
542
|
+
|
543
|
+
|
544
|
+
|
545
|
+
it 'should not call the hook stack if the hookable method is overwritten and does not call super' do
|
546
|
+
@class.before(:hookable) { hi_mom! }
|
547
|
+
@child = Class.new(@class) do
|
548
|
+
def hookable; end;
|
549
|
+
end
|
550
|
+
|
551
|
+
inst = @child.new
|
552
|
+
inst.should_not_receive(:hi_mom!)
|
553
|
+
inst.hookable
|
554
|
+
end
|
555
|
+
|
556
|
+
it 'should not call hooks defined in the child class for a hookable method in a parent if the child overwrites the hookable method without calling super' do
|
557
|
+
@child = Class.new(@class) do
|
558
|
+
before(:hookable) { hi_mom! }
|
559
|
+
def hookable; end;
|
560
|
+
end
|
561
|
+
|
562
|
+
inst = @child.new
|
563
|
+
inst.should_not_receive(:hi_mom!)
|
564
|
+
inst.hookable
|
565
|
+
end
|
566
|
+
|
567
|
+
it 'should not call hooks defined in child class even if hook method exists in parent' do
|
568
|
+
@class.send(:define_method, :hello_world) { hello_world! }
|
569
|
+
@child = Class.new(@class) do
|
570
|
+
before(:hookable, :hello_world)
|
571
|
+
end
|
572
|
+
|
573
|
+
inst = @class.new
|
574
|
+
inst.should_not_receive(:hello_world!)
|
575
|
+
inst.hookable
|
576
|
+
end
|
577
|
+
end
|
578
|
+
|
579
|
+
end
|
580
|
+
|
581
|
+
describe "hook invocation with module inclusions / extensions" do
|
582
|
+
|
583
|
+
describe "for class methods" do
|
584
|
+
it "should not overwrite methods included by extensions after the hook is declared" do
|
585
|
+
@module.class_eval do
|
586
|
+
@another_module = Module.new do
|
587
|
+
def greet; greetings_from_another_module; super; end;
|
588
|
+
end
|
589
|
+
|
590
|
+
def self.extended(base)
|
591
|
+
base.before_class_method(:clakable, :greet)
|
592
|
+
base.extend(@another_module)
|
593
|
+
end
|
594
|
+
end
|
595
|
+
|
596
|
+
@class.extend(@module)
|
597
|
+
@class.should_receive(:greetings_from_another_module).once.ordered
|
598
|
+
@class.should_receive(:greetings_from_module).once.ordered
|
599
|
+
@class.clakable
|
600
|
+
end
|
601
|
+
end
|
602
|
+
|
603
|
+
describe "for instance methods" do
|
604
|
+
it 'should not overwrite methods included by modules after the hook is declared' do
|
605
|
+
@module.class_eval do
|
606
|
+
@another_module = Module.new do
|
607
|
+
def greet; greetings_from_another_module; super; end;
|
608
|
+
end
|
609
|
+
|
610
|
+
def self.included(base)
|
611
|
+
base.before(:hookable, :greet)
|
612
|
+
base.send(:include, @another_module)
|
613
|
+
end
|
614
|
+
end
|
615
|
+
|
616
|
+
@class.send(:include, @module)
|
617
|
+
|
618
|
+
inst = @class.new
|
619
|
+
inst.should_receive(:greetings_from_another_module).once.ordered
|
620
|
+
inst.should_receive(:greetings_from_module).once.ordered
|
621
|
+
inst.hookable
|
622
|
+
end
|
623
|
+
end
|
624
|
+
|
625
|
+
end
|
626
|
+
|
627
|
+
describe "hook invocation with unrelated classes" do
|
628
|
+
|
629
|
+
describe "for class methods" do
|
630
|
+
it "should not execute hooks registered in an unrelated class" do
|
631
|
+
@class.before_class_method(:clakable) { hi_mom! }
|
632
|
+
|
633
|
+
@other.should_not_receive(:hi_mom!)
|
634
|
+
@other.clakable
|
635
|
+
end
|
636
|
+
end
|
637
|
+
|
638
|
+
describe "for instance methods" do
|
639
|
+
it "should not execute hooks registered in an unrelated class" do
|
640
|
+
@class.before(:hookable) { hi_mom! }
|
641
|
+
|
642
|
+
inst = @other.new
|
643
|
+
inst.should_not_receive(:hi_mom!)
|
644
|
+
inst.hookable
|
645
|
+
end
|
646
|
+
end
|
647
|
+
|
648
|
+
end
|
649
|
+
|
650
|
+
describe "using before hook" do
|
651
|
+
|
652
|
+
describe "for class methods" do
|
653
|
+
|
654
|
+
it 'should run the advice before the advised method' do
|
655
|
+
@class.class_eval %{def self.hook_me; second!; end;}
|
656
|
+
@class.register_class_hooks(:hook_me)
|
657
|
+
@class.before_class_method(:hook_me, :first!)
|
658
|
+
|
659
|
+
@class.should_receive(:first!).ordered
|
660
|
+
@class.should_receive(:second!).ordered
|
661
|
+
@class.hook_me
|
662
|
+
end
|
663
|
+
|
664
|
+
it 'should execute all advices once in order' do
|
665
|
+
@class.before_class_method(:clakable, :hook_1)
|
666
|
+
@class.before_class_method(:clakable, :hook_2)
|
667
|
+
@class.before_class_method(:clakable, :hook_3)
|
668
|
+
|
669
|
+
@class.should_receive(:hook_1).once.ordered
|
670
|
+
@class.should_receive(:hook_2).once.ordered
|
671
|
+
@class.should_receive(:hook_3).once.ordered
|
672
|
+
@class.clakable
|
673
|
+
end
|
674
|
+
end
|
675
|
+
|
676
|
+
describe "for instance methods" do
|
677
|
+
|
678
|
+
it 'should run the advice before the advised method' do
|
679
|
+
@class.class_eval %{
|
680
|
+
def hook_me; second!; end;
|
681
|
+
}
|
682
|
+
@class.register_instance_hooks(:hook_me)
|
683
|
+
@class.before(:hook_me, :first!)
|
684
|
+
|
685
|
+
inst = @class.new
|
686
|
+
inst.should_receive(:first!).ordered
|
687
|
+
inst.should_receive(:second!).ordered
|
688
|
+
inst.hook_me
|
689
|
+
end
|
690
|
+
|
691
|
+
it 'should execute all advices once in order' do
|
692
|
+
@class.before(:hookable, :hook_1)
|
693
|
+
@class.before(:hookable, :hook_2)
|
694
|
+
@class.before(:hookable, :hook_3)
|
695
|
+
|
696
|
+
inst = @class.new
|
697
|
+
inst.should_receive(:hook_1).once.ordered
|
698
|
+
inst.should_receive(:hook_2).once.ordered
|
699
|
+
inst.should_receive(:hook_3).once.ordered
|
700
|
+
inst.hookable
|
701
|
+
end
|
702
|
+
end
|
703
|
+
|
704
|
+
end
|
705
|
+
|
706
|
+
describe 'using after hook' do
|
707
|
+
|
708
|
+
describe "for class methods" do
|
709
|
+
|
710
|
+
it 'should run the advice after the advised method' do
|
711
|
+
@class.class_eval %{def self.hook_me; first!; end;}
|
712
|
+
@class.register_class_hooks(:hook_me)
|
713
|
+
@class.after_class_method(:hook_me, :second!)
|
714
|
+
|
715
|
+
@class.should_receive(:first!).ordered
|
716
|
+
@class.should_receive(:second!).ordered
|
717
|
+
@class.hook_me
|
718
|
+
end
|
719
|
+
|
720
|
+
it 'should execute all advices once in order' do
|
721
|
+
@class.after_class_method(:clakable, :hook_1)
|
722
|
+
@class.after_class_method(:clakable, :hook_2)
|
723
|
+
@class.after_class_method(:clakable, :hook_3)
|
724
|
+
|
725
|
+
@class.should_receive(:hook_1).once.ordered
|
726
|
+
@class.should_receive(:hook_2).once.ordered
|
727
|
+
@class.should_receive(:hook_3).once.ordered
|
728
|
+
@class.clakable
|
729
|
+
end
|
730
|
+
|
731
|
+
it "the advised method should still return its normal value" do
|
732
|
+
@class.class_eval %{def self.hello; "hello world"; end;}
|
733
|
+
@class.register_class_hooks(:hello)
|
734
|
+
@class.after_class_method(:hello) { "BAM" }
|
735
|
+
|
736
|
+
@class.hello.should == "hello world"
|
737
|
+
end
|
738
|
+
|
739
|
+
end
|
740
|
+
|
741
|
+
describe "for instance methods" do
|
742
|
+
|
743
|
+
it 'should run the advice after the advised method' do
|
744
|
+
@class.class_eval %{def hook_me; first!; end;}
|
745
|
+
@class.register_instance_hooks(:hook_me)
|
746
|
+
@class.after(:hook_me, :second!)
|
747
|
+
|
748
|
+
inst = @class.new
|
749
|
+
inst.should_receive(:first!).ordered
|
750
|
+
inst.should_receive(:second!).ordered
|
751
|
+
inst.hook_me
|
752
|
+
end
|
753
|
+
|
754
|
+
it 'should execute all advices once in order' do
|
755
|
+
@class.after(:hookable, :hook_1)
|
756
|
+
@class.after(:hookable, :hook_2)
|
757
|
+
@class.after(:hookable, :hook_3)
|
758
|
+
|
759
|
+
inst = @class.new
|
760
|
+
inst.should_receive(:hook_1).once.ordered
|
761
|
+
inst.should_receive(:hook_2).once.ordered
|
762
|
+
inst.should_receive(:hook_3).once.ordered
|
763
|
+
inst.hookable
|
764
|
+
end
|
765
|
+
|
766
|
+
it "the advised method should still return its normal value" do
|
767
|
+
@class.class_eval %{def hello; "hello world"; end;}
|
768
|
+
@class.register_instance_hooks(:hello)
|
769
|
+
@class.after(:hello) { "BAM" }
|
770
|
+
|
771
|
+
@class.new.hello.should == "hello world"
|
772
|
+
end
|
773
|
+
|
774
|
+
end
|
775
|
+
|
776
|
+
end
|
777
|
+
|
778
|
+
describe 'aborting' do
|
779
|
+
|
780
|
+
describe "for class methods" do
|
781
|
+
it "should catch :halt from a before hook and abort the advised method" do
|
782
|
+
@class.class_eval %{def self.no_love; love_me!; end;}
|
783
|
+
@class.register_class_hooks :no_love
|
784
|
+
@class.before_class_method(:no_love) { maybe! }
|
785
|
+
@class.before_class_method(:no_love) { throw :halt }
|
786
|
+
@class.before_class_method(:no_love) { what_about_me? }
|
787
|
+
|
788
|
+
@class.should_receive(:maybe!)
|
789
|
+
@class.should_not_receive(:what_about_me?)
|
790
|
+
@class.should_not_receive(:love_me!)
|
791
|
+
lambda { @class.no_love }.should_not throw_symbol(:halt)
|
792
|
+
end
|
793
|
+
|
794
|
+
it "should not run after hooks if a before hook throws :halt" do
|
795
|
+
@class.before_class_method(:clakable) { throw :halt }
|
796
|
+
@class.after_class_method(:clakable) { bam! }
|
797
|
+
|
798
|
+
@class.should_not_receive(:bam!)
|
799
|
+
lambda { @class.clakable }.should_not throw_symbol(:halt)
|
800
|
+
end
|
801
|
+
|
802
|
+
it "should return nil from the hookable method if a before hook throws :halt" do
|
803
|
+
@class.class_eval %{def self.with_value; "hello"; end;}
|
804
|
+
@class.register_class_hooks(:with_value)
|
805
|
+
@class.before_class_method(:with_value) { throw :halt }
|
806
|
+
|
807
|
+
@class.with_value.should be_nil
|
808
|
+
end
|
809
|
+
|
810
|
+
it "should catch :halt from an after hook and cease the advice" do
|
811
|
+
@class.after_class_method(:clakable) { throw :halt }
|
812
|
+
@class.after_class_method(:clakable) { never_see_me! }
|
813
|
+
|
814
|
+
@class.should_not_receive(:never_see_me!)
|
815
|
+
lambda { @class.clakable }.should_not throw_symbol(:halt)
|
816
|
+
end
|
817
|
+
|
818
|
+
it "should still return the hookable methods return value if an after hook throws :halt" do
|
819
|
+
@class.class_eval %{def self.with_value; "hello"; end;}
|
820
|
+
@class.register_class_hooks(:with_value)
|
821
|
+
@class.after_class_method(:with_value) { throw :halt }
|
822
|
+
|
823
|
+
@class.with_value.should == "hello"
|
824
|
+
end
|
825
|
+
end
|
826
|
+
|
827
|
+
describe "for instance methods" do
|
828
|
+
it "should catch :halt from a before hook and abort the advised method" do
|
829
|
+
@class.class_eval %{def no_love; love_me!; end;}
|
830
|
+
@class.register_instance_hooks :no_love
|
831
|
+
@class.before(:no_love) { maybe! }
|
832
|
+
@class.before(:no_love) { throw :halt }
|
833
|
+
@class.before(:no_love) { what_about_me? }
|
834
|
+
|
835
|
+
inst = @class.new
|
836
|
+
inst.should_receive(:maybe!)
|
837
|
+
inst.should_not_receive(:what_about_me?)
|
838
|
+
inst.should_not_receive(:love_me!)
|
839
|
+
lambda { inst.no_love }.should_not throw_symbol(:halt)
|
840
|
+
end
|
841
|
+
|
842
|
+
it "should not run after hooks if a before hook throws :halt" do
|
843
|
+
@class.before(:hookable) { throw :halt }
|
844
|
+
@class.after(:hookable) { bam! }
|
845
|
+
|
846
|
+
inst = @class.new
|
847
|
+
inst.should_not_receive(:bam!)
|
848
|
+
lambda { inst.hookable }.should_not throw_symbol(:halt)
|
849
|
+
end
|
850
|
+
|
851
|
+
it "should return nil from the hookable method if a before hook throws :halt" do
|
852
|
+
@class.class_eval %{def with_value; "hello"; end;}
|
853
|
+
@class.register_instance_hooks(:with_value)
|
854
|
+
@class.before(:with_value) { throw :halt }
|
855
|
+
|
856
|
+
@class.new.with_value.should be_nil
|
857
|
+
end
|
858
|
+
|
859
|
+
it "should catch :halt from an after hook and cease the advice" do
|
860
|
+
@class.after(:hookable) { throw :halt }
|
861
|
+
@class.after(:hookable) { never_see_me! }
|
862
|
+
|
863
|
+
inst = @class.new
|
864
|
+
inst.should_not_receive(:never_see_me!)
|
865
|
+
lambda { inst.hookable }.should_not throw_symbol(:halt)
|
866
|
+
end
|
867
|
+
|
868
|
+
it "should still return the hookable methods return value if an after hook throws :halt" do
|
869
|
+
@class.class_eval %{def with_value; "hello"; end;}
|
870
|
+
@class.register_instance_hooks(:with_value)
|
871
|
+
@class.after(:with_value) { throw :halt }
|
872
|
+
|
873
|
+
@class.new.with_value.should == "hello"
|
874
|
+
end
|
875
|
+
end
|
876
|
+
|
877
|
+
end
|
878
|
+
|
879
|
+
describe "helper methods" do
|
880
|
+
it 'should generate the correct argument signature' do
|
881
|
+
@class.class_eval do
|
882
|
+
def some_method(a, b, c)
|
883
|
+
[a, b, c]
|
884
|
+
end
|
885
|
+
|
886
|
+
def yet_another(a, *heh)p
|
887
|
+
[a, *heh]
|
888
|
+
end
|
889
|
+
end
|
890
|
+
|
891
|
+
@class.args_for(@class.instance_method(:hookable)).should == ""
|
892
|
+
@class.args_for(@class.instance_method(:some_method)).should == "_1, _2, _3"
|
893
|
+
@class.args_for(@class.instance_method(:yet_another)).should == "_1, *args"
|
894
|
+
end
|
895
|
+
end
|
896
|
+
|
897
|
+
end
|