motion-support 0.2.3 → 0.2.4
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.
- data/lib/motion-support/callbacks.rb +9 -0
- data/lib/motion-support/concern.rb +7 -0
- data/motion/_stdlib/array.rb +13 -0
- data/motion/_stdlib/enumerable.rb +9 -0
- data/motion/callbacks.rb +511 -0
- data/motion/concern.rb +122 -0
- data/motion/core_ext/kernel/singleton_class.rb +6 -0
- data/motion/version.rb +1 -1
- data/spec/motion-support/callback_spec.rb +702 -0
- data/spec/motion-support/concern_spec.rb +93 -0
- data/spec/motion-support/core_ext/kernel/singleton_class_spec.rb +9 -0
- metadata +17 -4
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
|
data/motion/version.rb
CHANGED
@@ -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
|