aspectory 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.
@@ -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