aspectory 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,36 @@
1
+ module Aspectory
2
+ class ObservedMethod
3
+ attr_reader :method_id
4
+
5
+ def initialize(method_id, options={})
6
+ @method_id = method_id
7
+ @handlers = []
8
+ @count = 0
9
+ @times = options[:times] || 1
10
+ end
11
+
12
+ def match(sym)
13
+ return unless case method_id
14
+ when Symbol then valid? and method_id === sym
15
+ when Regexp then valid? and method_id.try(:match, sym.to_s)
16
+ else ; nil
17
+ end
18
+ @handlers.each { |fn| fn[sym] }
19
+ @count += 1
20
+ end
21
+
22
+ def valid?
23
+ if @times.to_s.match(/^(inf|any|all|every)/)
24
+ def self.valid?; true end
25
+ true # memoizing the result
26
+ else
27
+ @count < @times
28
+ end
29
+ end
30
+
31
+ def push(*args)
32
+ @handlers += args
33
+ @handlers.tap.compact!.uniq!
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,5 @@
1
+ class Method
2
+ def arity_match?(collection)
3
+ arity == -1 or arity == collection.length
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe Aspectory do
4
+ it "exists, so this spec doesn't blow up" do
5
+ Aspectory
6
+ end
7
+ end
@@ -0,0 +1,655 @@
1
+ require 'spec/spec_helper'
2
+
3
+ describe Aspectory::Callbacker do
4
+ attr_reader :klass, :subklass, :callbacker, :object
5
+
6
+ before(:each) do
7
+ @klass = Class.new do
8
+ attr_reader :results
9
+
10
+ def initialize
11
+ @results = []
12
+ end
13
+
14
+ def no
15
+ false
16
+ end
17
+
18
+ def foo(arg=:foo, &block)
19
+ @results << (block_given? ? block.call : arg)
20
+ arg
21
+ end
22
+
23
+ def bar(arg=:bar, &block)
24
+ return arg unless arg
25
+ @results << (block_given? ? block.call : arg)
26
+ end
27
+
28
+ def bar!(arg)
29
+ @results << arg
30
+ end
31
+
32
+ def bar=(arg)
33
+ @results << arg
34
+ end
35
+
36
+ def bar?(arg)
37
+ @results << (arg == :bar)
38
+ end
39
+
40
+ def wrapify
41
+ @results << :before
42
+ yield
43
+ @results << :after
44
+ end
45
+
46
+ def pitch
47
+ throw :foo, :result
48
+ end
49
+ end
50
+
51
+ @callbacker = Aspectory::Callbacker.new(klass)
52
+ @object = klass.new
53
+ end
54
+
55
+ describe "klass#__PRISTINE__" do
56
+ it "allows original method calling" do
57
+ callbacker.before(:foo) { @results << :before }
58
+
59
+ object.__PRISTINE__(:foo)
60
+ object.results.should == [:foo]
61
+ end
62
+
63
+ it "allows arguments" do
64
+ callbacker.before(:foo) { @results << :before }
65
+
66
+ object.__PRISTINE__(:foo, :bar)
67
+ object.results.should == [:bar]
68
+ end
69
+
70
+ it "allows a block" do
71
+ callbacker.before(:foo) { @results << :before }
72
+
73
+ object.__PRISTINE__(:foo) { :bar }
74
+ object.results.should == [:bar]
75
+ end
76
+
77
+ it "raises when method doesn't exist" do
78
+ proc {
79
+ callbacker.__PRISTINE__(:whiz)
80
+ }.should raise_error(NoMethodError)
81
+ end
82
+
83
+ describe "*_without_callbacks methods" do
84
+ it "are generated for methods with callbacks" do
85
+ callbacker.before(:foo) { @results << :before }
86
+
87
+ object.foo_without_callbacks
88
+ object.results.should == [:foo]
89
+ end
90
+
91
+ it "are generated for bang methods" do
92
+ callbacker.before(:bar!) { @results << :before }
93
+
94
+ object.bar_without_callbacks! :bar
95
+ object.results.should == [:bar]
96
+ end
97
+
98
+ it "are generated for predicate methods" do
99
+ callbacker.before(:bar?) { @results << :before }
100
+
101
+ object.bar_without_callbacks? :bar
102
+ object.results.should == [true]
103
+ end
104
+
105
+ it "are generated for assignment methods" do
106
+ callbacker.before(:bar=) { @results << :before }
107
+
108
+ object.bar_without_callbacks = :bar
109
+ object.results.should == [:bar]
110
+ end
111
+ end
112
+ end
113
+
114
+ describe "#before" do
115
+ context "with a block" do
116
+ it "defines before behavior" do
117
+ callbacker.before(:foo) { @results << :before }
118
+
119
+ object.foo
120
+ object.results.should == [:before, :foo]
121
+ end
122
+
123
+ describe "special method name endings" do
124
+ it "works with bang methods" do
125
+ callbacker.before(:bar=) { @results << :banged }
126
+ object.bar = :bar
127
+ object.results.should == [:banged, :bar]
128
+ end
129
+
130
+ it "works with predicate methods" do
131
+ callbacker.before(:bar?) { @results << :banged }
132
+ object.bar?(:bar)
133
+ object.results.should == [:banged, true]
134
+ end
135
+ end
136
+
137
+ describe "subclass behavior" do
138
+ before(:each) do
139
+ callbacker.before(:foo) { @results << :before }
140
+ @subklass = Class.new(klass)
141
+ @object = @subklass.new
142
+ end
143
+
144
+ it "runs superclass' callbacks" do
145
+ object.foo
146
+ object.results.should == [:before, :foo]
147
+ end
148
+
149
+ it "works with subclasses of subclasses" do
150
+ subsubklass = Class.new(subklass)
151
+ subobject = subsubklass.new
152
+ subobject.foo
153
+ subobject.results.should == [:before, :foo]
154
+ end
155
+
156
+ it "has subclass specific callbacks" do
157
+ callbacker.before(:bar) { @results << :subbed }
158
+ object.bar
159
+ object.results.should == [:before, :subbed, :bar]
160
+ end
161
+ end
162
+
163
+ describe "redefining methods" do
164
+ it "allows arguments" do
165
+ callbacker.before(:foo) { @results << :before }
166
+
167
+ object.foo(:arg)
168
+ object.results.should == [:before, :arg]
169
+ end
170
+
171
+ it "allows a block" do
172
+ callbacker.before(:foo) { @results << :before }
173
+
174
+ object.foo { :block }
175
+ object.results.should == [:before, :block]
176
+ end
177
+
178
+ it "only happens once" do
179
+ mock(callbacker).redefine_method(anything).once
180
+ callbacker.before(:foo) { true }
181
+ callbacker.before(:foo) { false }
182
+ end
183
+ end
184
+
185
+ describe "callback blocks" do
186
+ it "enables halting of method call" do
187
+ callbacker.before(:foo) { false }
188
+
189
+ object.foo
190
+ object.results.should be_empty
191
+ end
192
+
193
+ it "can be more than one per method" do
194
+ callbacker.before(:foo) { ping! }
195
+ callbacker.before(:foo) { pong! }
196
+
197
+ mock(object) do |expect|
198
+ expect.ping!
199
+ expect.pong!
200
+ end
201
+
202
+ object.foo
203
+ end
204
+
205
+ describe "throwing alternative result" do
206
+ before(:each) do
207
+ callbacker.before(:foo) { throw :foo, :result }
208
+ end
209
+
210
+ it "returns alternative" do
211
+ object.foo.should == :result
212
+ end
213
+
214
+ it "doesn't run original method" do
215
+ object.results.should be_empty
216
+ end
217
+ end
218
+ end
219
+ end
220
+
221
+ context "with a symbol" do
222
+ it "defines before behavior" do
223
+ callbacker.before(:foo, :bar)
224
+
225
+ object.foo
226
+ object.results.should == [:bar, :foo]
227
+ end
228
+
229
+ describe "redefining methods" do
230
+ it "allows arguments" do
231
+ callbacker.before(:foo, :bar)
232
+
233
+ object.foo(:arg)
234
+ object.results.should == [:bar, :arg]
235
+ end
236
+
237
+ it "allows a block" do
238
+ callbacker.before(:foo, :bar)
239
+
240
+ object.foo { :block }
241
+ object.results.should == [:bar, :block]
242
+ end
243
+ end
244
+
245
+ describe "callback blocks" do
246
+ it "enables halting of method call" do
247
+ callbacker.before(:foo, :no)
248
+
249
+ object.foo
250
+ object.results.should be_empty
251
+ end
252
+
253
+ it "can be more than one per method" do
254
+ callbacker.before(:foo, :ping!)
255
+ callbacker.before(:foo, :pong!)
256
+
257
+ mock(object) do |expect|
258
+ expect.ping!
259
+ expect.pong!
260
+ end
261
+
262
+ object.foo
263
+ end
264
+
265
+ it "doesn't run same callback twice for same method" do
266
+ callbacker.before(:foo, :ping!)
267
+ callbacker.before(:foo, :ping!)
268
+
269
+ mock(object).ping!.once
270
+
271
+ object.foo
272
+ end
273
+ end
274
+ end
275
+ end
276
+
277
+ describe "#after" do
278
+ context "with a block" do
279
+ it "defines after behavior" do
280
+ callbacker.after(:foo) { @results << :after }
281
+
282
+ object.foo
283
+ object.results.should == [:foo, :after]
284
+ end
285
+
286
+ describe "redefining methods" do
287
+ it "allows arguments" do
288
+ callbacker.after(:foo) { @results << :after }
289
+
290
+ object.foo(:arg)
291
+ object.results.should == [:arg, :after]
292
+ end
293
+
294
+ it "allows a block" do
295
+ callbacker.after(:foo) { @results << :after }
296
+
297
+ object.foo { :block }
298
+ object.results.should == [:block, :after]
299
+ end
300
+
301
+ it "only happens once" do
302
+ mock(callbacker).redefine_method(anything).once
303
+
304
+ callbacker.after(:bar) { true }
305
+ callbacker.after(:bar) { false }
306
+ end
307
+ end
308
+
309
+ describe "special method name endings" do
310
+ it "works with bang methods" do
311
+ callbacker.after(:bar=) { @results << :banged }
312
+ object.bar = :bar
313
+ object.results.should == [:bar, :banged]
314
+ end
315
+
316
+ it "works with predicate methods" do
317
+ callbacker.after(:bar?) { @results << :banged }
318
+ object.bar?(:bar)
319
+ object.results.should == [true, :banged]
320
+ end
321
+ end
322
+
323
+ describe "subclass behavior" do
324
+ before(:each) do
325
+ callbacker.after(:foo) { @results << :after }
326
+ @subklass = Class.new(klass)
327
+ @object = @subklass.new
328
+ end
329
+
330
+ it "runs superclass' callbacks" do
331
+ object.foo
332
+ object.results.should == [:foo, :after]
333
+ end
334
+
335
+ it "works with subclasses of subclasses" do
336
+ subsubklass = Class.new(subklass)
337
+ subobject = subsubklass.new
338
+ subobject.foo
339
+ subobject.results.should == [:foo, :after]
340
+ end
341
+
342
+ it "has subclass specific callbacks" do
343
+ callbacker.after(:bar) { @results << :subbed }
344
+ object.bar
345
+ object.results.should == [:bar, :after, :subbed]
346
+ end
347
+ end
348
+
349
+ describe "callback blocks" do
350
+ it "cannot enable halting of method call" do
351
+ callbacker.after(:foo) { false }
352
+
353
+ object.foo
354
+ object.results.should == [:foo]
355
+ end
356
+
357
+ it "still gets called when method returns false" do
358
+ callbacker.after(:bar?) { @results << :called }
359
+ object.bar?(:foo)
360
+ object.results.should == [false, :called]
361
+ end
362
+
363
+ it "can be more than one per method" do
364
+ callbacker.after(:foo) { ping! }
365
+ callbacker.after(:foo) { pong! }
366
+
367
+ mock(object).ping!
368
+ mock(object).pong!
369
+
370
+ object.foo
371
+ end
372
+
373
+ it "gets access to result of method call" do
374
+ callbacker.after(:foo) { |result| @results << result }
375
+
376
+ object.foo
377
+ object.results.should == [:foo, :foo]
378
+ end
379
+
380
+ it "can throw alternative result" do
381
+ callbacker.after(:foo) { throw :foo, :result }
382
+
383
+ object.foo.should == :result
384
+ end
385
+ end
386
+ end
387
+
388
+ context "with a symbol" do
389
+ it "defines after behavior" do
390
+ callbacker.after(:foo, :bar?)
391
+
392
+ object.foo
393
+ object.results.should == [:foo, false]
394
+ end
395
+
396
+ it "doesn't run same callback twice for same method" do
397
+ callbacker.after(:foo, :ping!)
398
+ callbacker.after(:foo, :ping!)
399
+
400
+ mock(object).ping!(anything).once
401
+
402
+ object.foo
403
+ end
404
+
405
+ describe "redefining methods" do
406
+ it "allows arguments" do
407
+ callbacker.after(:foo, :bar?)
408
+
409
+ object.foo(:bar)
410
+ object.results.should == [:bar, true]
411
+ end
412
+
413
+ it "allows a block" do
414
+ callbacker.after(:foo, :bar)
415
+
416
+ object.foo { :block }
417
+ object.results.should == [:block, :foo]
418
+ end
419
+ end
420
+
421
+ describe "callback blocks" do
422
+ it "cannot enable halting of method call" do
423
+ callbacker.after(:foo, :no)
424
+
425
+ object.foo
426
+ object.results.should == [:foo]
427
+ end
428
+
429
+ it "gets access to result of method call" do
430
+ callbacker.after(:foo, :bar)
431
+
432
+ object.foo(:arg)
433
+ object.results.should == [:arg, :arg]
434
+ end
435
+
436
+ it "can be more than one per method" do
437
+ callbacker.after(:foo, :ping!)
438
+ callbacker.after(:foo, :pong!)
439
+
440
+ mock(object).ping!.with(:foo)
441
+ mock(object).pong!.with(:foo)
442
+
443
+ object.foo
444
+ end
445
+
446
+ it "can throw alternative result" do
447
+ callbacker.after(:foo, :pitch)
448
+
449
+ object.foo.should == :result
450
+ end
451
+ end
452
+ end
453
+ end
454
+
455
+ describe "#around" do
456
+ context "with a block" do
457
+ it "returns proper result" do
458
+ callbacker.around(:foo) do |fn|
459
+ @results << :before
460
+ fn.call
461
+ @results << :after
462
+ end
463
+
464
+ object.foo.should == :foo
465
+ end
466
+
467
+ it "defines after behavior" do
468
+ callbacker.around(:foo) do |fn|
469
+ @results << :before
470
+ fn.call
471
+ @results << :after
472
+ end
473
+
474
+ object.foo
475
+ object.results.should == [:before, :foo, :after]
476
+ end
477
+
478
+ describe "redefining methods" do
479
+ it "allows arguments" do
480
+ callbacker.around(:foo) do |fn|
481
+ @results << :before
482
+ fn.call
483
+ @results << :after
484
+ end
485
+
486
+ object.foo(:arg)
487
+ object.results.should == [:before, :arg, :after]
488
+ end
489
+
490
+ it "allows a block" do
491
+ callbacker.around(:foo) do |fn|
492
+ @results << :before
493
+ fn.call
494
+ @results << :after
495
+ end
496
+
497
+ object.foo { :block }
498
+ object.results.should == [:before, :block, :after]
499
+ end
500
+
501
+ it "only happens once" do
502
+ mock(callbacker).redefine_method(anything).once
503
+
504
+ callbacker.around(:bar) { true }
505
+ callbacker.around(:bar) { false }
506
+ end
507
+ end
508
+
509
+ describe "special method name endings" do
510
+ it "works with bang methods" do
511
+ callbacker.around(:bar!) do |fn|
512
+ @results << :before
513
+ fn.call
514
+ @results << :after
515
+ end
516
+ object.bar! :bar
517
+ object.results.should == [:before, :bar, :after]
518
+ end
519
+
520
+ it "works with predicate methods" do
521
+ callbacker.around(:bar?) do |fn|
522
+ @results << :before
523
+ fn.call
524
+ @results << :after
525
+ end
526
+ object.bar?(:bar)
527
+ object.results.should == [:before, true, :after]
528
+ end
529
+
530
+ it "works with assignment methods" do
531
+ callbacker.around(:bar=) do |fn|
532
+ @results << :before
533
+ fn.call
534
+ @results << :after
535
+ end
536
+ object.bar = :bar
537
+ object.results.should == [:before, :bar, :after]
538
+ end
539
+ end
540
+
541
+ describe "subclass behavior" do
542
+ before(:each) do
543
+ callbacker.around(:foo) do |fn|
544
+ @results << :before
545
+ fn.call
546
+ @results << :after
547
+ end
548
+ @subklass = Class.new(klass)
549
+ @object = @subklass.new
550
+ end
551
+
552
+ it "runs superclass' callbacks" do
553
+ object.foo
554
+ object.results.should == [:before, :foo, :after]
555
+ end
556
+
557
+ it "works with subclasses of subclasses" do
558
+ subsubklass = Class.new(subklass)
559
+ subobject = subsubklass.new
560
+ subobject.foo
561
+ subobject.results.should == [:before, :foo, :after]
562
+ end
563
+
564
+ it "has subclass specific callbacks" do
565
+ callbacker.after(:bar) { @results << :subbed }
566
+ object.bar
567
+ object.results.should == [:before, :bar, :after, :subbed]
568
+ end
569
+ end
570
+
571
+ describe "callback blocks" do
572
+ it "can enable halting of method call" do
573
+ callbacker.around(:foo) { false }
574
+
575
+ object.foo
576
+ object.results.should be_empty
577
+ end
578
+
579
+ it "can be more than one per method" do
580
+ callbacker.around(:foo) { ping! }
581
+ callbacker.around(:foo) { pong! }
582
+
583
+ mock(object).ping!
584
+ mock(object).pong!
585
+
586
+ object.foo
587
+ end
588
+
589
+ it "can throw alternative result" do
590
+ callbacker.around(:foo) { |fn| fn.call and throw :foo, :result }
591
+
592
+ object.foo.should == :result
593
+ end
594
+ end
595
+ end
596
+
597
+ context "with a symbol" do
598
+ it "returns proper result" do
599
+ callbacker.around(:foo, :wrapify)
600
+
601
+ object.foo.should == :foo
602
+ end
603
+
604
+ it "defines after behavior" do
605
+ callbacker.around(:foo, :wrapify)
606
+
607
+ object.foo
608
+ object.results.should == [:before, :foo, :after]
609
+ end
610
+
611
+ it "doesn't run same callback twice for same method" do
612
+ callbacker.around(:foo, :ping!)
613
+ callbacker.around(:foo, :ping!)
614
+
615
+ mock(object).ping!.once
616
+
617
+ object.foo
618
+ end
619
+
620
+ describe "redefining methods" do
621
+ it "allows arguments" do
622
+ callbacker.around(:foo, :wrapify)
623
+
624
+ object.foo(:bar)
625
+ object.results.should == [:before, :bar, :after]
626
+ end
627
+
628
+ it "allows a block" do
629
+ callbacker.around(:foo, :wrapify)
630
+
631
+ object.foo { :block }
632
+ object.results.should == [:before, :block, :after]
633
+ end
634
+ end
635
+
636
+ describe "callback blocks" do
637
+ it "can be more than one per method" do
638
+ callbacker.around(:foo, :ping!)
639
+ callbacker.around(:foo, :pong!)
640
+
641
+ mock(object).ping!
642
+ mock(object).pong!
643
+
644
+ object.foo
645
+ end
646
+
647
+ it "can throw alternative result" do
648
+ callbacker.around(:foo, :pitch)
649
+
650
+ object.foo.should == :result
651
+ end
652
+ end
653
+ end
654
+ end
655
+ end