motion-support 0.2.3 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
data/motion/concern.rb ADDED
@@ -0,0 +1,122 @@
1
+ module MotionSupport
2
+ # A typical module looks like this:
3
+ #
4
+ # module M
5
+ # def self.included(base)
6
+ # base.extend ClassMethods
7
+ # base.class_eval do
8
+ # scope :disabled, -> { where(disabled: true) }
9
+ # end
10
+ # end
11
+ #
12
+ # module ClassMethods
13
+ # ...
14
+ # end
15
+ # end
16
+ #
17
+ # By using <tt>MotionSupport::Concern</tt> the above module could instead be
18
+ # written as:
19
+ #
20
+ # module M
21
+ # extend MotionSupport::Concern
22
+ #
23
+ # included do
24
+ # scope :disabled, -> { where(disabled: true) }
25
+ # end
26
+ #
27
+ # module ClassMethods
28
+ # ...
29
+ # end
30
+ # end
31
+ #
32
+ # Moreover, it gracefully handles module dependencies. Given a +Foo+ module
33
+ # and a +Bar+ module which depends on the former, we would typically write the
34
+ # following:
35
+ #
36
+ # module Foo
37
+ # def self.included(base)
38
+ # base.class_eval do
39
+ # def self.method_injected_by_foo
40
+ # ...
41
+ # end
42
+ # end
43
+ # end
44
+ # end
45
+ #
46
+ # module Bar
47
+ # def self.included(base)
48
+ # base.method_injected_by_foo
49
+ # end
50
+ # end
51
+ #
52
+ # class Host
53
+ # include Foo # We need to include this dependency for Bar
54
+ # include Bar # Bar is the module that Host really needs
55
+ # end
56
+ #
57
+ # But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We
58
+ # could try to hide these from +Host+ directly including +Foo+ in +Bar+:
59
+ #
60
+ # module Bar
61
+ # include Foo
62
+ # def self.included(base)
63
+ # base.method_injected_by_foo
64
+ # end
65
+ # end
66
+ #
67
+ # class Host
68
+ # include Bar
69
+ # end
70
+ #
71
+ # Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt>
72
+ # is the +Bar+ module, not the +Host+ class. With <tt>MotionSupport::Concern</tt>,
73
+ # module dependencies are properly resolved:
74
+ #
75
+ # module Foo
76
+ # extend MotionSupport::Concern
77
+ # included do
78
+ # def self.method_injected_by_foo
79
+ # ...
80
+ # end
81
+ # end
82
+ # end
83
+ #
84
+ # module Bar
85
+ # extend MotionSupport::Concern
86
+ # include Foo
87
+ #
88
+ # included do
89
+ # self.method_injected_by_foo
90
+ # end
91
+ # end
92
+ #
93
+ # class Host
94
+ # include Bar # works, Bar takes care now of its dependencies
95
+ # end
96
+ module Concern
97
+ def self.extended(base) #:nodoc:
98
+ base.instance_variable_set("@_dependencies", [])
99
+ end
100
+
101
+ def append_features(base)
102
+ if base.instance_variable_defined?("@_dependencies")
103
+ base.instance_variable_get("@_dependencies") << self
104
+ return false
105
+ else
106
+ return false if base < self
107
+ @_dependencies.each { |dep| base.send(:include, dep) }
108
+ super
109
+ base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
110
+ base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
111
+ end
112
+ end
113
+
114
+ def included(base = nil, &block)
115
+ if base.nil?
116
+ @_included_block = block
117
+ else
118
+ super
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,6 @@
1
+ module Kernel
2
+ # class_eval on an object acts like singleton_class.class_eval.
3
+ def class_eval(*args, &block)
4
+ singleton_class.class_eval(*args, &block)
5
+ end
6
+ end
data/motion/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module MotionSupport
2
- VERSION = "0.2.3"
2
+ VERSION = "0.2.4"
3
3
  end
@@ -0,0 +1,702 @@
1
+ module CallbacksTest
2
+ class Record
3
+ include MotionSupport::Callbacks
4
+
5
+ define_callbacks :save
6
+
7
+ def self.before_save(*filters, &blk)
8
+ set_callback(:save, :before, *filters, &blk)
9
+ end
10
+
11
+ def self.after_save(*filters, &blk)
12
+ set_callback(:save, :after, *filters, &blk)
13
+ end
14
+
15
+ class << self
16
+ def callback_symbol(callback_method)
17
+ method_name = :"#{callback_method}_method"
18
+ define_method(method_name) do
19
+ history << [callback_method, :symbol]
20
+ end
21
+ method_name
22
+ end
23
+
24
+ def callback_proc(callback_method)
25
+ Proc.new { |model| model.history << [callback_method, :proc] }
26
+ end
27
+
28
+ def callback_object(callback_method)
29
+ klass = Class.new
30
+ klass.send(:define_method, callback_method) do |model|
31
+ model.history << [:"#{callback_method}_save", :object]
32
+ end
33
+ klass.new
34
+ end
35
+ end
36
+
37
+ def history
38
+ @history ||= []
39
+ end
40
+ end
41
+
42
+ class Person < Record
43
+ [:before_save, :after_save].each do |callback_method|
44
+ callback_method_sym = callback_method.to_sym
45
+ send(callback_method, callback_symbol(callback_method_sym))
46
+ send(callback_method, callback_proc(callback_method_sym))
47
+ send(callback_method, callback_object(callback_method_sym.to_s.gsub(/_save/, '')))
48
+ send(callback_method) { |model| model.history << [callback_method_sym, :block] }
49
+ end
50
+
51
+ def save
52
+ run_callbacks :save
53
+ end
54
+ end
55
+
56
+ class PersonSkipper < Person
57
+ skip_callback :save, :before, :before_save_method, :if => :yes
58
+ skip_callback :save, :after, :before_save_method, :unless => :yes
59
+ skip_callback :save, :after, :before_save_method, :if => :no
60
+ skip_callback :save, :before, :before_save_method, :unless => :no
61
+ def yes; true; end
62
+ def no; false; end
63
+ end
64
+
65
+ class ParentController
66
+ include MotionSupport::Callbacks
67
+
68
+ define_callbacks :dispatch
69
+
70
+ set_callback :dispatch, :before, :log, :unless => proc {|c| c.action_name == :index || c.action_name == :show }
71
+ set_callback :dispatch, :after, :log2
72
+
73
+ attr_reader :action_name, :logger
74
+ def initialize(action_name)
75
+ @action_name, @logger = action_name, []
76
+ end
77
+
78
+ def log
79
+ @logger << action_name
80
+ end
81
+
82
+ def log2
83
+ @logger << action_name
84
+ end
85
+
86
+ def dispatch
87
+ run_callbacks :dispatch do
88
+ @logger << "Done"
89
+ end
90
+ self
91
+ end
92
+ end
93
+
94
+ class Child < ParentController
95
+ skip_callback :dispatch, :before, :log, :if => proc {|c| c.action_name == :update}
96
+ skip_callback :dispatch, :after, :log2
97
+ end
98
+
99
+ class OneTimeCompile < Record
100
+ @@starts_true, @@starts_false = true, false
101
+
102
+ def initialize
103
+ super
104
+ end
105
+
106
+ before_save Proc.new {|r| r.history << [:before_save, :starts_true, :if] }, :if => :starts_true
107
+ before_save Proc.new {|r| r.history << [:before_save, :starts_false, :if] }, :if => :starts_false
108
+ before_save Proc.new {|r| r.history << [:before_save, :starts_true, :unless] }, :unless => :starts_true
109
+ before_save Proc.new {|r| r.history << [:before_save, :starts_false, :unless] }, :unless => :starts_false
110
+
111
+ def starts_true
112
+ if @@starts_true
113
+ @@starts_true = false
114
+ return true
115
+ end
116
+ @@starts_true
117
+ end
118
+
119
+ def starts_false
120
+ unless @@starts_false
121
+ @@starts_false = true
122
+ return false
123
+ end
124
+ @@starts_false
125
+ end
126
+
127
+ def save
128
+ run_callbacks :save
129
+ end
130
+ end
131
+
132
+ class AfterSaveConditionalPerson < Record
133
+ after_save Proc.new { |r| r.history << [:after_save, :string1] }
134
+ after_save Proc.new { |r| r.history << [:after_save, :string2] }
135
+ def save
136
+ run_callbacks :save
137
+ end
138
+ end
139
+
140
+ class ConditionalPerson < Record
141
+ # proc
142
+ before_save Proc.new { |r| r.history << [:before_save, :proc] }, :if => Proc.new { |r| true }
143
+ before_save Proc.new { |r| r.history << "b00m" }, :if => Proc.new { |r| false }
144
+ before_save Proc.new { |r| r.history << [:before_save, :proc] }, :unless => Proc.new { |r| false }
145
+ before_save Proc.new { |r| r.history << "b00m" }, :unless => Proc.new { |r| true }
146
+ # symbol
147
+ before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :if => :yes
148
+ before_save Proc.new { |r| r.history << "b00m" }, :if => :no
149
+ before_save Proc.new { |r| r.history << [:before_save, :symbol] }, :unless => :no
150
+ before_save Proc.new { |r| r.history << "b00m" }, :unless => :yes
151
+ # string
152
+ before_save Proc.new { |r| r.history << [:before_save, :string] }, :if => 'yes'
153
+ before_save Proc.new { |r| r.history << "b00m" }, :if => 'no'
154
+ before_save Proc.new { |r| r.history << [:before_save, :string] }, :unless => 'no'
155
+ before_save Proc.new { |r| r.history << "b00m" }, :unless => 'yes'
156
+ # Combined if and unless
157
+ before_save Proc.new { |r| r.history << [:before_save, :combined_symbol] }, :if => :yes, :unless => :no
158
+ before_save Proc.new { |r| r.history << "b00m" }, :if => :yes, :unless => :yes
159
+
160
+ def yes; true; end
161
+ def other_yes; true; end
162
+ def no; false; end
163
+ def other_no; false; end
164
+
165
+ def save
166
+ run_callbacks :save
167
+ end
168
+ end
169
+
170
+ class CleanPerson < ConditionalPerson
171
+ reset_callbacks :save
172
+ end
173
+
174
+ class MySuper
175
+ include MotionSupport::Callbacks
176
+ define_callbacks :save
177
+ end
178
+
179
+ class AroundPerson < MySuper
180
+ attr_reader :history
181
+
182
+ set_callback :save, :before, :nope, :if => :no
183
+ set_callback :save, :before, :nope, :unless => :yes
184
+ set_callback :save, :after, :tweedle
185
+ set_callback :save, :before, "tweedle_dee"
186
+ set_callback :save, :before, proc {|m| m.history << "yup" }
187
+ set_callback :save, :before, :nope, :if => proc { false }
188
+ set_callback :save, :before, :nope, :unless => proc { true }
189
+ set_callback :save, :before, :yup, :if => proc { true }
190
+ set_callback :save, :before, :yup, :unless => proc { false }
191
+ set_callback :save, :around, :tweedle_dum
192
+ set_callback :save, :around, :w0tyes, :if => :yes
193
+ set_callback :save, :around, :w0tno, :if => :no
194
+ set_callback :save, :around, :tweedle_deedle
195
+
196
+ def no; false; end
197
+ def yes; true; end
198
+
199
+ def nope
200
+ @history << "boom"
201
+ end
202
+
203
+ def yup
204
+ @history << "yup"
205
+ end
206
+
207
+ def w0tyes
208
+ @history << "w0tyes before"
209
+ yield
210
+ @history << "w0tyes after"
211
+ end
212
+
213
+ def w0tno
214
+ @history << "boom"
215
+ yield
216
+ end
217
+
218
+ def tweedle_dee
219
+ @history << "tweedle dee"
220
+ end
221
+
222
+ def tweedle_dum
223
+ @history << "tweedle dum pre"
224
+ yield
225
+ @history << "tweedle dum post"
226
+ end
227
+
228
+ def tweedle
229
+ @history << "tweedle"
230
+ end
231
+
232
+ def tweedle_deedle
233
+ @history << "tweedle deedle pre"
234
+ yield
235
+ @history << "tweedle deedle post"
236
+ end
237
+
238
+ def initialize
239
+ @history = []
240
+ end
241
+
242
+ def save
243
+ run_callbacks :save do
244
+ @history << "running"
245
+ end
246
+ end
247
+ end
248
+
249
+ class AroundPersonResult < MySuper
250
+ attr_reader :result
251
+
252
+ set_callback :save, :after, :tweedle_1
253
+ set_callback :save, :around, :tweedle_dum
254
+ set_callback :save, :after, :tweedle_2
255
+
256
+ def tweedle_dum
257
+ @result = yield
258
+ end
259
+
260
+ def tweedle_1
261
+ :tweedle_1
262
+ end
263
+
264
+ def tweedle_2
265
+ :tweedle_2
266
+ end
267
+
268
+ def save
269
+ run_callbacks :save do
270
+ :running
271
+ end
272
+ end
273
+ end
274
+
275
+ class HyphenatedCallbacks
276
+ include MotionSupport::Callbacks
277
+ define_callbacks :save
278
+ attr_reader :stuff
279
+
280
+ set_callback :save, :before, :action, :if => :yes
281
+
282
+ def yes() true end
283
+
284
+ def action
285
+ @stuff = "ACTION"
286
+ end
287
+
288
+ def save
289
+ run_callbacks :save do
290
+ @stuff
291
+ end
292
+ end
293
+ end
294
+
295
+ module ExtendModule
296
+ def self.extended(base)
297
+ base.class_eval do
298
+ set_callback :save, :before, :record3
299
+ end
300
+ end
301
+ def record3
302
+ @recorder << 3
303
+ end
304
+ end
305
+
306
+ module IncludeModule
307
+ def self.included(base)
308
+ base.class_eval do
309
+ set_callback :save, :before, :record2
310
+ end
311
+ end
312
+ def record2
313
+ @recorder << 2
314
+ end
315
+ end
316
+
317
+ class ExtendCallbacks
318
+
319
+ include MotionSupport::Callbacks
320
+
321
+ define_callbacks :save
322
+ set_callback :save, :before, :record1
323
+
324
+ include IncludeModule
325
+
326
+ def save
327
+ run_callbacks :save
328
+ end
329
+
330
+ attr_reader :recorder
331
+
332
+ def initialize
333
+ @recorder = []
334
+ end
335
+
336
+ private
337
+
338
+ def record1
339
+ @recorder << 1
340
+ end
341
+ end
342
+
343
+ class CallbackTerminator
344
+ include MotionSupport::Callbacks
345
+
346
+ define_callbacks :save, :terminator => lambda { |result| result == :halt }
347
+
348
+ set_callback :save, :before, :first
349
+ set_callback :save, :before, :second
350
+ set_callback :save, :around, :around_it
351
+ set_callback :save, :before, :third
352
+ set_callback :save, :after, :first
353
+ set_callback :save, :around, :around_it
354
+ set_callback :save, :after, :second
355
+ set_callback :save, :around, :around_it
356
+ set_callback :save, :after, :third
357
+
358
+
359
+ attr_reader :history, :saved, :halted
360
+ def initialize
361
+ @history = []
362
+ end
363
+
364
+ def around_it
365
+ @history << "around1"
366
+ yield
367
+ @history << "around2"
368
+ end
369
+
370
+ def first
371
+ @history << "first"
372
+ end
373
+
374
+ def second
375
+ @history << "second"
376
+ :halt
377
+ end
378
+
379
+ def third
380
+ @history << "third"
381
+ end
382
+
383
+ def save
384
+ run_callbacks :save do
385
+ @saved = true
386
+ end
387
+ end
388
+
389
+ def halted_callback_hook(filter)
390
+ @halted = filter
391
+ end
392
+ end
393
+
394
+ class CallbackObject
395
+ def before(caller)
396
+ caller.record << "before"
397
+ end
398
+
399
+ def before_save(caller)
400
+ caller.record << "before save"
401
+ end
402
+
403
+ def around(caller)
404
+ caller.record << "around before"
405
+ yield
406
+ caller.record << "around after"
407
+ end
408
+ end
409
+
410
+ class UsingObjectBefore
411
+ include MotionSupport::Callbacks
412
+
413
+ define_callbacks :save
414
+ set_callback :save, :before, CallbackObject.new
415
+
416
+ attr_accessor :record
417
+ def initialize
418
+ @record = []
419
+ end
420
+
421
+ def save
422
+ run_callbacks :save do
423
+ @record << "yielded"
424
+ end
425
+ end
426
+ end
427
+
428
+ class UsingObjectAround
429
+ include MotionSupport::Callbacks
430
+
431
+ define_callbacks :save
432
+ set_callback :save, :around, CallbackObject.new
433
+
434
+ attr_accessor :record
435
+ def initialize
436
+ @record = []
437
+ end
438
+
439
+ def save
440
+ run_callbacks :save do
441
+ @record << "yielded"
442
+ end
443
+ end
444
+ end
445
+
446
+ class CustomScopeObject
447
+ include MotionSupport::Callbacks
448
+
449
+ define_callbacks :save, :scope => [:kind, :name]
450
+ set_callback :save, :before, CallbackObject.new
451
+
452
+ attr_accessor :record
453
+ def initialize
454
+ @record = []
455
+ end
456
+
457
+ def save
458
+ run_callbacks :save do
459
+ @record << "yielded"
460
+ "CallbackResult"
461
+ end
462
+ end
463
+ end
464
+
465
+ class OneTwoThreeSave
466
+ include MotionSupport::Callbacks
467
+
468
+ define_callbacks :save
469
+
470
+ attr_accessor :record
471
+
472
+ def initialize
473
+ @record = []
474
+ end
475
+
476
+ def save
477
+ run_callbacks :save do
478
+ @record << "yielded"
479
+ end
480
+ end
481
+
482
+ def first
483
+ @record << "one"
484
+ end
485
+
486
+ def second
487
+ @record << "two"
488
+ end
489
+
490
+ def third
491
+ @record << "three"
492
+ end
493
+ end
494
+
495
+ class DuplicatingCallbacks < OneTwoThreeSave
496
+ set_callback :save, :before, :first, :second
497
+ set_callback :save, :before, :first, :third
498
+ end
499
+
500
+ class DuplicatingCallbacksInSameCall < OneTwoThreeSave
501
+ set_callback :save, :before, :first, :second, :first, :third
502
+ end
503
+
504
+ class WriterSkipper < Person
505
+ attr_accessor :age
506
+ skip_callback :save, :before, :before_save_method, :if => lambda { |obj| obj.age > 21 }
507
+ end
508
+ end
509
+
510
+ describe "Callbacks" do
511
+ describe "around filter" do
512
+ it "should optimize on first compile" do
513
+ around = CallbacksTest::OneTimeCompile.new
514
+ around.save
515
+ around.history.should == [
516
+ [:before_save, :starts_true, :if],
517
+ [:before_save, :starts_true, :unless]
518
+ ]
519
+ end
520
+
521
+ it "should call nested around filter" do
522
+ around = CallbacksTest::AroundPerson.new
523
+ around.save
524
+ around.history.should == [
525
+ "tweedle dee",
526
+ "yup", "yup",
527
+ "tweedle dum pre",
528
+ "w0tyes before",
529
+ "tweedle deedle pre",
530
+ "running",
531
+ "tweedle deedle post",
532
+ "w0tyes after",
533
+ "tweedle dum post",
534
+ "tweedle"
535
+ ]
536
+ end
537
+
538
+ it "should return result" do
539
+ around = CallbacksTest::AroundPersonResult.new
540
+ around.save
541
+ around.result.should == :running
542
+ end
543
+ end
544
+
545
+ describe "after save" do
546
+ it "should run in reverse order" do
547
+ person = CallbacksTest::AfterSaveConditionalPerson.new
548
+ person.save
549
+ person.history.should == [
550
+ [:after_save, :string2],
551
+ [:after_save, :string1]
552
+ ]
553
+ end
554
+ end
555
+
556
+ describe "skip callbacks" do
557
+ it "should skip callback" do
558
+ person = CallbacksTest::PersonSkipper.new
559
+ person.history.should == []
560
+ person.save
561
+ person.history.should == [
562
+ [:before_save, :proc],
563
+ [:before_save, :object],
564
+ [:before_save, :block],
565
+ [:after_save, :block],
566
+ [:after_save, :object],
567
+ [:after_save, :proc],
568
+ [:after_save, :symbol]
569
+ ]
570
+ end
571
+
572
+ it "should not skip if condition is not met" do
573
+ writer = CallbacksTest::WriterSkipper.new
574
+ writer.age = 18
575
+ writer.history.should == []
576
+ writer.save
577
+ writer.history.should == [
578
+ [:before_save, :symbol],
579
+ [:before_save, :proc],
580
+ [:before_save, :object],
581
+ [:before_save, :block],
582
+ [:after_save, :block],
583
+ [:after_save, :object],
584
+ [:after_save, :proc],
585
+ [:after_save, :symbol]
586
+ ]
587
+ end
588
+ end
589
+
590
+ it "should run callbacks" do
591
+ person = CallbacksTest::Person.new
592
+ person.history.should == []
593
+ person.save
594
+ person.history.should == [
595
+ [:before_save, :symbol],
596
+ [:before_save, :proc],
597
+ [:before_save, :object],
598
+ [:before_save, :block],
599
+ [:after_save, :block],
600
+ [:after_save, :object],
601
+ [:after_save, :proc],
602
+ [:after_save, :symbol]
603
+ ]
604
+ end
605
+
606
+ it "should run conditional callbacks" do
607
+ person = CallbacksTest::ConditionalPerson.new
608
+ person.save
609
+ person.history.should == [
610
+ [:before_save, :proc],
611
+ [:before_save, :proc],
612
+ [:before_save, :symbol],
613
+ [:before_save, :symbol],
614
+ [:before_save, :string],
615
+ [:before_save, :string],
616
+ [:before_save, :combined_symbol],
617
+ ]
618
+ end
619
+
620
+ it "should return result" do
621
+ obj = CallbacksTest::HyphenatedCallbacks.new
622
+ obj.save
623
+ obj.stuff.should == "ACTION"
624
+ end
625
+
626
+ describe "reset_callbacks" do
627
+ it "should reset callbacks" do
628
+ person = CallbacksTest::CleanPerson.new
629
+ person.save
630
+ person.history.should == []
631
+ end
632
+ end
633
+
634
+ describe "callback object" do
635
+ it "should use callback object for before callback" do
636
+ u = CallbacksTest::UsingObjectBefore.new
637
+ u.save
638
+ u.record.should == ["before", "yielded"]
639
+ end
640
+
641
+ it "should use callback object for around callback" do
642
+ u = CallbacksTest::UsingObjectAround.new
643
+ u.save
644
+ u.record.should == ["around before", "yielded", "around after"]
645
+ end
646
+
647
+ describe "custom scope" do
648
+ it "should use custom scope" do
649
+ u = CallbacksTest::CustomScopeObject.new
650
+ u.save
651
+ u.record.should == ["before save", "yielded"]
652
+ end
653
+
654
+ it "should return block result" do
655
+ u = CallbacksTest::CustomScopeObject.new
656
+ u.save.should == "CallbackResult"
657
+ end
658
+ end
659
+ end
660
+
661
+ describe "callback terminator" do
662
+ it "should terminate" do
663
+ terminator = CallbacksTest::CallbackTerminator.new
664
+ terminator.save
665
+ terminator.history.should == ["first", "second", "third", "second", "first"]
666
+ end
667
+
668
+ it "should invoke hook" do
669
+ terminator = CallbacksTest::CallbackTerminator.new
670
+ terminator.save
671
+ terminator.halted.should == ":second"
672
+ end
673
+
674
+ it "should never call block if terminated" do
675
+ obj = CallbacksTest::CallbackTerminator.new
676
+ obj.save
677
+ obj.saved.should.be.nil
678
+ end
679
+ end
680
+
681
+ describe "extending object" do
682
+ it "should work with extending object" do
683
+ model = CallbacksTest::ExtendCallbacks.new.extend CallbacksTest::ExtendModule
684
+ model.save
685
+ model.recorder.should == [1, 2, 3]
686
+ end
687
+ end
688
+
689
+ describe "exclude duplicates" do
690
+ it "should exclude duplicates in separate calls" do
691
+ model = CallbacksTest::DuplicatingCallbacks.new
692
+ model.save
693
+ model.record.should == ["two", "one", "three", "yielded"]
694
+ end
695
+
696
+ it "should exclude duplicates in one call" do
697
+ model = CallbacksTest::DuplicatingCallbacksInSameCall.new
698
+ model.save
699
+ model.record.should == ["two", "one", "three", "yielded"]
700
+ end
701
+ end
702
+ end