Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Newer
Older
100644 746 lines (659 sloc) 23.757 kb
63c8ea9 adds a couple of missing AS dependencies
Rolf Timmermans authored
1 require 'active_support/concern'
a2b7fcb @josevalim Change callbacks to automatically include DescendantsTracker and rena…
josevalim authored
2 require 'active_support/descendants_tracker'
269b463 @gsamokovarov Further clean-up of ActiveSupport::Callbacks
gsamokovarov authored
3 require 'active_support/core_ext/array/extract_options'
ae44bf7 @joshk bye bye extlib_inheritable_*, AS callbacks now using class_attribute
joshk authored
4 require 'active_support/core_ext/class/attribute'
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
5 require 'active_support/core_ext/kernel/reporting'
89978f1 @fxn moves Object#singleton_class to Kernel#singleton_class to match Ruby …
fxn authored
6 require 'active_support/core_ext/kernel/singleton_class'
132db31 @tenderlove make the compile method thread safe
tenderlove authored
7 require 'thread'
f28bd95 @jeremy Fix dependencies revealed by testing in isolation
jeremy authored
8
aae37bb @jeremy Extract ActiveSupport::Callbacks from Active Record, test case setup …
jeremy authored
9 module ActiveSupport
10fffd7 @aayushkhandelwal11 typos rectified lifecycle => life cycle
aayushkhandelwal11 authored
10 # Callbacks are code hooks that are run at key points in an object's life cycle.
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
11 # The typical use case is to have a base class define a set of callbacks
12 # relevant to the other functionality it supplies, so that subclasses can
13 # install callbacks that enhance or modify the base functionality without
14 # needing to override or redefine methods of the base class.
5932357 @lifo Docs for ActiveSupport::Callbacks. Closes #11254 [ernesto.jimenez]
lifo authored
15 #
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
16 # Mixing in this module allows you to define the events in the object's
10fffd7 @aayushkhandelwal11 typos rectified lifecycle => life cycle
aayushkhandelwal11 authored
17 # life cycle that will support callbacks (via +ClassMethods.define_callbacks+),
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
18 # set the instance methods, procs, or callback objects to be called (via
019df98 @claudiob Replace comments' non-breaking spaces with spaces
claudiob authored
19 # +ClassMethods.set_callback+), and run the installed callbacks at the
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
20 # appropriate times (via +run_callbacks+).
5932357 @lifo Docs for ActiveSupport::Callbacks. Closes #11254 [ernesto.jimenez]
lifo authored
21 #
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
22 # Three kinds of callbacks are supported: before callbacks, run before a
23 # certain event; after callbacks, run after the event; and around callbacks,
24 # blocks that surround the event, triggering it when they yield. Callback code
25 # can be contained in instance methods, procs or lambdas, or callback objects
26 # that respond to certain predetermined methods. See +ClassMethods.set_callback+
27 # for details.
5932357 @lifo Docs for ActiveSupport::Callbacks. Closes #11254 [ernesto.jimenez]
lifo authored
28 #
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
29 # class Record
30 # include ActiveSupport::Callbacks
31 # define_callbacks :save
5932357 @lifo Docs for ActiveSupport::Callbacks. Closes #11254 [ernesto.jimenez]
lifo authored
32 #
33 # def save
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
34 # run_callbacks :save do
35 # puts "- save"
36 # end
5932357 @lifo Docs for ActiveSupport::Callbacks. Closes #11254 [ernesto.jimenez]
lifo authored
37 # end
38 # end
39 #
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
40 # class PersonRecord < Record
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
41 # set_callback :save, :before, :saving_message
5932357 @lifo Docs for ActiveSupport::Callbacks. Closes #11254 [ernesto.jimenez]
lifo authored
42 # def saving_message
43 # puts "saving..."
44 # end
45 #
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
46 # set_callback :save, :after do |object|
5932357 @lifo Docs for ActiveSupport::Callbacks. Closes #11254 [ernesto.jimenez]
lifo authored
47 # puts "saved"
48 # end
49 # end
50 #
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
51 # person = PersonRecord.new
52 # person.save
5932357 @lifo Docs for ActiveSupport::Callbacks. Closes #11254 [ernesto.jimenez]
lifo authored
53 #
54 # Output:
55 # saving...
56 # - save
57 # saved
aae37bb @jeremy Extract ActiveSupport::Callbacks from Active Record, test case setup …
jeremy authored
58 module Callbacks
7b169ed @josh Extend Callbacks and Rescuable with AS concern
josh authored
59 extend Concern
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
60
a2b7fcb @josevalim Change callbacks to automatically include DescendantsTracker and rena…
josevalim authored
61 included do
62 extend ActiveSupport::DescendantsTracker
63 end
64
d46cf35 @vipulnsward extract array to a constant
vipulnsward authored
65 CALLBACK_FILTER_TYPES = [:before, :after, :around]
66
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
67 # Runs the callbacks for the given event.
68 #
69 # Calls the before and around callbacks in the order they were set, yields
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
70 # the block (if given one), and then runs the after callbacks in reverse
71 # order.
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
72 #
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
73 # If the callback chain was halted, returns +false+. Otherwise returns the
74 # result of the block, or +true+ if no block is given.
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
75 #
76 # run_callbacks :save do
77 # save
78 # end
c1ce414 @frodsan AS::Callbacks#run_callbacks remove key argument
frodsan authored
79 def run_callbacks(kind, &block)
156e2e3 @tenderlove Revert "just call the class method since we know the callbacks are st…
tenderlove authored
80 cbs = send("_#{kind}_callbacks")
ccbefff @tenderlove if there is nothing to compile, then do not bother compiling
tenderlove authored
81 if cbs.empty?
82 yield if block_given?
83 else
84 runner = cbs.compile
85 e = Filters::Environment.new(self, false, nil, block)
86 runner.call(e).value
87 end
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
88 end
89
38ab982 @josevalim Log 'Filter chain halted as CALLBACKNAME rendered or redirected' ever…
josevalim authored
90 private
91
4a9e54e @aayushkhandelwal11 typos rectified [ci skip]
aayushkhandelwal11 authored
92 # A hook invoked every time a before callback is halted.
103a313 @makaroni4 fix typo in documentation
makaroni4 authored
93 # This can be overridden in AS::Callback implementors in order
38ab982 @josevalim Log 'Filter chain halted as CALLBACKNAME rendered or redirected' ever…
josevalim authored
94 # to provide better debugging/logging.
95 def halted_callback_hook(filter)
96 end
97
a63a964 @tenderlove remove some evals from callback conditionals
tenderlove authored
98 module Conditionals # :nodoc:
99 class Value
100 def initialize(&block)
101 @block = block
102 end
103 def call(target, value); @block.call(value); end
104 end
105 end
106
3551690 @tenderlove use an environment object to hold state about the filter calls
tenderlove authored
107 module Filters
108 Environment = Struct.new(:target, :halted, :value, :run_block)
73aefee @tenderlove use a singleton end node
tenderlove authored
109
110 class End
111 def call(env)
112 block = env.run_block
113 env.value = !env.halted && (!block || block.call)
114 env
115 end
116 end
117 ENDING = End.new
eac50bc @tenderlove polymorphic before callbacks
tenderlove authored
118
119 class Before
120 def self.build(next_callback, user_callback, user_conditions, chain_config, filter)
ba55276 @tenderlove deprecating string based terminators
tenderlove authored
121 halted_lambda = chain_config[:terminator]
122
eac50bc @tenderlove polymorphic before callbacks
tenderlove authored
123 if chain_config.key?(:terminator) && user_conditions.any?
c5953f7 @tenderlove rename terminal to halting, try to keep naming consistent
tenderlove authored
124 halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
eac50bc @tenderlove polymorphic before callbacks
tenderlove authored
125 elsif chain_config.key? :terminator
c5953f7 @tenderlove rename terminal to halting, try to keep naming consistent
tenderlove authored
126 halting(next_callback, user_callback, halted_lambda, filter)
eac50bc @tenderlove polymorphic before callbacks
tenderlove authored
127 elsif user_conditions.any?
bd95ff8 @tenderlove push the before filter lambdas to factory methods
tenderlove authored
128 conditional(next_callback, user_callback, user_conditions)
eac50bc @tenderlove polymorphic before callbacks
tenderlove authored
129 else
bd95ff8 @tenderlove push the before filter lambdas to factory methods
tenderlove authored
130 simple next_callback, user_callback
eac50bc @tenderlove polymorphic before callbacks
tenderlove authored
131 end
132 end
bd95ff8 @tenderlove push the before filter lambdas to factory methods
tenderlove authored
133
134 private
135
c5953f7 @tenderlove rename terminal to halting, try to keep naming consistent
tenderlove authored
136 def self.halting_and_conditional(next_callback, user_callback, user_conditions, halted_lambda, filter)
bd95ff8 @tenderlove push the before filter lambdas to factory methods
tenderlove authored
137 lambda { |env|
138 target = env.target
139 value = env.value
140 halted = env.halted
141
142 if !halted && user_conditions.all? { |c| c.call(target, value) }
143 result = user_callback.call target, value
ba55276 @tenderlove deprecating string based terminators
tenderlove authored
144 env.halted = halted_lambda.call(target, result)
bd95ff8 @tenderlove push the before filter lambdas to factory methods
tenderlove authored
145 if env.halted
146 target.send :halted_callback_hook, filter
147 end
148 end
149 next_callback.call env
150 }
151 end
152
c5953f7 @tenderlove rename terminal to halting, try to keep naming consistent
tenderlove authored
153 def self.halting(next_callback, user_callback, halted_lambda, filter)
bd95ff8 @tenderlove push the before filter lambdas to factory methods
tenderlove authored
154 lambda { |env|
155 target = env.target
156 value = env.value
157 halted = env.halted
158
d433436 @tenderlove use unless instead of if!
tenderlove authored
159 unless halted
bd95ff8 @tenderlove push the before filter lambdas to factory methods
tenderlove authored
160 result = user_callback.call target, value
ba55276 @tenderlove deprecating string based terminators
tenderlove authored
161 env.halted = halted_lambda.call(target, result)
bd95ff8 @tenderlove push the before filter lambdas to factory methods
tenderlove authored
162 if env.halted
163 target.send :halted_callback_hook, filter
164 end
165 end
166 next_callback.call env
167 }
168 end
169
170 def self.conditional(next_callback, user_callback, user_conditions)
171 lambda { |env|
172 target = env.target
173 value = env.value
174
175 if user_conditions.all? { |c| c.call(target, value) }
176 user_callback.call target, value
177 end
178 next_callback.call env
179 }
180 end
181
182 def self.simple(next_callback, user_callback)
183 lambda { |env|
184 user_callback.call env.target, env.value
185 next_callback.call env
186 }
187 end
eac50bc @tenderlove polymorphic before callbacks
tenderlove authored
188 end
e8ddd4f @tenderlove polymorphic after filter
tenderlove authored
189
190 class After
191 def self.build(next_callback, user_callback, user_conditions, chain_config)
192 if chain_config[:skip_after_callbacks_if_terminated]
193 if chain_config.key?(:terminator) && user_conditions.any?
194 halting_and_conditional(next_callback, user_callback, user_conditions)
195 elsif chain_config.key?(:terminator)
196 halting(next_callback, user_callback)
197 elsif user_conditions.any?
198 conditional next_callback, user_callback, user_conditions
199 else
200 simple next_callback, user_callback
201 end
202 else
203 if user_conditions.any?
204 conditional next_callback, user_callback, user_conditions
205 else
206 simple next_callback, user_callback
207 end
208 end
209 end
210
211 private
212
213 def self.halting_and_conditional(next_callback, user_callback, user_conditions)
214 lambda { |env|
215 env = next_callback.call env
216 target = env.target
217 value = env.value
218 halted = env.halted
219
220 if !halted && user_conditions.all? { |c| c.call(target, value) }
221 user_callback.call target, value
222 end
223 env
224 }
225 end
226
227 def self.halting(next_callback, user_callback)
228 lambda { |env|
229 env = next_callback.call env
d433436 @tenderlove use unless instead of if!
tenderlove authored
230 unless env.halted
e8ddd4f @tenderlove polymorphic after filter
tenderlove authored
231 user_callback.call env.target, env.value
232 end
233 env
234 }
235 end
236
237 def self.conditional(next_callback, user_callback, user_conditions)
238 lambda { |env|
239 env = next_callback.call env
240 target = env.target
241 value = env.value
242
243 if user_conditions.all? { |c| c.call(target, value) }
244 user_callback.call target, value
245 end
246 env
247 }
248 end
249
250 def self.simple(next_callback, user_callback)
251 lambda { |env|
252 env = next_callback.call env
253 user_callback.call env.target, env.value
254 env
255 }
256 end
257 end
87378b0 @tenderlove polymorphic around callbacks
tenderlove authored
258
259 class Around
260 def self.build(next_callback, user_callback, user_conditions, chain_config)
261 if chain_config.key?(:terminator) && user_conditions.any?
262 halting_and_conditional(next_callback, user_callback, user_conditions)
263 elsif chain_config.key? :terminator
264 halting(next_callback, user_callback)
265 elsif user_conditions.any?
266 conditional(next_callback, user_callback, user_conditions)
267 else
268 simple(next_callback, user_callback)
269 end
270 end
271
272 private
273
274 def self.halting_and_conditional(next_callback, user_callback, user_conditions)
275 lambda { |env|
276 target = env.target
277 value = env.value
278 halted = env.halted
279
280 if !halted && user_conditions.all? { |c| c.call(target, value) }
281 user_callback.call(target, value) {
282 env = next_callback.call env
283 env.value
284 }
285 env
286 else
287 next_callback.call env
288 end
289 }
290 end
291
292 def self.halting(next_callback, user_callback)
293 lambda { |env|
294 target = env.target
295 value = env.value
296
d433436 @tenderlove use unless instead of if!
tenderlove authored
297 unless env.halted
87378b0 @tenderlove polymorphic around callbacks
tenderlove authored
298 user_callback.call(target, value) {
299 env = next_callback.call env
300 env.value
301 }
302 env
303 else
304 next_callback.call env
305 end
306 }
307 end
308
309 def self.conditional(next_callback, user_callback, user_conditions)
310 lambda { |env|
311 target = env.target
312 value = env.value
313
314 if user_conditions.all? { |c| c.call(target, value) }
315 user_callback.call(target, value) {
316 env = next_callback.call env
317 env.value
318 }
319 env
320 else
321 next_callback.call env
322 end
323 }
324 end
325
326 def self.simple(next_callback, user_callback)
327 lambda { |env|
328 user_callback.call(env.target, env.value) {
329 env = next_callback.call env
330 env.value
331 }
332 env
333 }
334 end
335 end
3551690 @tenderlove use an environment object to hold state about the filter calls
tenderlove authored
336 end
337
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
338 class Callback #:nodoc:#
2efe6ce @tenderlove remove klass because it is not used
tenderlove authored
339 def self.build(chain, filter, kind, options)
b97ff31 @tenderlove do not keep a reference to the chain in the callback objects
tenderlove authored
340 new chain.name, filter, kind, options, chain.config
b9903c3 @tenderlove polymorphic comparison operator
tenderlove authored
341 end
342
db78643 @tenderlove stop keeping a reference to the options hash
tenderlove authored
343 attr_accessor :kind, :name
b97ff31 @tenderlove do not keep a reference to the chain in the callback objects
tenderlove authored
344 attr_reader :chain_config
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
345
b97ff31 @tenderlove do not keep a reference to the chain in the callback objects
tenderlove authored
346 def initialize(name, filter, kind, options, chain_config)
347 @chain_config = chain_config
348 @name = name
40bdad3 @tenderlove rename instance variables
tenderlove authored
349 @kind = kind
350 @filter = filter
351 @key = compute_identifier filter
db78643 @tenderlove stop keeping a reference to the options hash
tenderlove authored
352 @if = Array(options[:if])
353 @unless = Array(options[:unless])
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
354 end
355
40bdad3 @tenderlove rename instance variables
tenderlove authored
356 def filter; @key; end
357 def raw_filter; @filter; end
9e323e7 @tenderlove separate identification computation
tenderlove authored
358
929658c @tenderlove push merge code to the callback itself
tenderlove authored
359 def merge(chain, new_options)
8559871 @tenderlove remove dead code
tenderlove authored
360 options = {
db78643 @tenderlove stop keeping a reference to the options hash
tenderlove authored
361 :if => @if.dup,
362 :unless => @unless.dup
91e002e @tenderlove dup the callback and set the chain
tenderlove authored
363 }
929658c @tenderlove push merge code to the callback itself
tenderlove authored
364
8559871 @tenderlove remove dead code
tenderlove authored
365 options[:if].concat Array(new_options.fetch(:unless, []))
366 options[:unless].concat Array(new_options.fetch(:if, []))
929658c @tenderlove push merge code to the callback itself
tenderlove authored
367
8559871 @tenderlove remove dead code
tenderlove authored
368 self.class.build chain, @filter, @kind, options
856a4dc @jeremy Refactor filters to use Active Support callbacks. Closes #11235.
jeremy authored
369 end
370
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
371 def matches?(_kind, _filter)
8038f7e @tenderlove separate filters from source code
tenderlove authored
372 @kind == _kind && filter == _filter
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
373 end
374
4a9644a @dmitriy-kiriyenko Prevent callback from being set twice.
dmitriy-kiriyenko authored
375 def duplicates?(other)
40bdad3 @tenderlove rename instance variables
tenderlove authored
376 case @filter
17dfee0 @tenderlove push duplicates? logic to the instance
tenderlove authored
377 when Symbol, String
378 matches?(other.kind, other.filter)
379 else
380 false
381 end
4a9644a @dmitriy-kiriyenko Prevent callback from being set twice.
dmitriy-kiriyenko authored
382 end
383
3dc80b7 @bogdan AS::Callbacks::Callback refactor
bogdan authored
384 # Wraps code with filter
2b1d7ea @tenderlove fix variable name
tenderlove authored
385 def apply(next_callback)
9b52026 @tenderlove fixing more variable names
tenderlove authored
386 user_conditions = conditions_lambdas
40bdad3 @tenderlove rename instance variables
tenderlove authored
387 user_callback = make_lambda @filter
23122ab @tenderlove callbacks are wrapped with lambdas
tenderlove authored
388
40bdad3 @tenderlove rename instance variables
tenderlove authored
389 case kind
1ce9b73 @pahanix Replace nested ifs with case/when
pahanix authored
390 when :before
eac50bc @tenderlove polymorphic before callbacks
tenderlove authored
391 Filters::Before.build(next_callback, user_callback, user_conditions, chain_config, @filter)
1ce9b73 @pahanix Replace nested ifs with case/when
pahanix authored
392 when :after
e8ddd4f @tenderlove polymorphic after filter
tenderlove authored
393 Filters::After.build(next_callback, user_callback, user_conditions, chain_config)
1ce9b73 @pahanix Replace nested ifs with case/when
pahanix authored
394 when :around
87378b0 @tenderlove polymorphic around callbacks
tenderlove authored
395 Filters::Around.build(next_callback, user_callback, user_conditions, chain_config)
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
396 end
856a4dc @jeremy Refactor filters to use Active Support callbacks. Closes #11235.
jeremy authored
397 end
398
399 private
400
bf6429c @tenderlove fix method names
tenderlove authored
401 def invert_lambda(l)
402 lambda { |*args, &blk| !l.call(*args, &blk) }
403 end
404
23122ab @tenderlove callbacks are wrapped with lambdas
tenderlove authored
405 # Filters support:
406 #
407 # Symbols:: A method to call.
408 # Strings:: Some content to evaluate.
409 # Procs:: A proc to call with the object.
410 # Objects:: An object with a <tt>before_foo</tt> method on it to call.
411 #
412 # All of these objects are compiled into methods and handled
413 # the same after this point:
414 #
415 # Symbols:: Already methods.
442c52e @prathamesh-sonpatki Fixed typos in activesupport [ci skip]
prathamesh-sonpatki authored
416 # Strings:: class_eval'd into methods.
417 # Procs:: using define_method compiled into methods.
23122ab @tenderlove callbacks are wrapped with lambdas
tenderlove authored
418 # Objects::
419 # a method is created that calls the before_foo method
420 # on the object.
bf6429c @tenderlove fix method names
tenderlove authored
421 def make_lambda(filter)
422 case filter
423 when Symbol
d2c0571 @tenderlove Revert "we never pass blocks, so remove this"
tenderlove authored
424 lambda { |target, _, &blk| target.send filter, &blk }
bf6429c @tenderlove fix method names
tenderlove authored
425 when String
426 l = eval "lambda { |value| #{filter} }"
b308f0d @tenderlove raise an argument error if the filter arity is greater than 1
tenderlove authored
427 lambda { |target, value| target.instance_exec(value, &l) }
a63a964 @tenderlove remove some evals from callback conditionals
tenderlove authored
428 when Conditionals::Value then filter
bf6429c @tenderlove fix method names
tenderlove authored
429 when ::Proc
dbee8c3 @tenderlove fixing arity2 test
tenderlove authored
430 if filter.arity > 1
431 return lambda { |target, _, &block|
432 raise ArgumentError unless block
433 target.instance_exec(target, block, &filter)
434 }
435 end
bf6429c @tenderlove fix method names
tenderlove authored
436
b308f0d @tenderlove raise an argument error if the filter arity is greater than 1
tenderlove authored
437 if filter.arity <= 0
438 lambda { |target, _| target.instance_exec(&filter) }
bf6429c @tenderlove fix method names
tenderlove authored
439 else
b308f0d @tenderlove raise an argument error if the filter arity is greater than 1
tenderlove authored
440 lambda { |target, _| target.instance_exec(target, &filter) }
bf6429c @tenderlove fix method names
tenderlove authored
441 end
442 else
b97ff31 @tenderlove do not keep a reference to the chain in the callback objects
tenderlove authored
443 scopes = Array(chain_config[:scope])
bf6429c @tenderlove fix method names
tenderlove authored
444 method_to_call = scopes.map{ |s| public_send(s) }.join("_")
445
446 lambda { |target, _, &blk|
447 filter.public_send method_to_call, target, &blk
448 }
449 end
450 end
451
9e323e7 @tenderlove separate identification computation
tenderlove authored
452 def compute_identifier(filter)
453 case filter
454 when String, ::Proc
455 filter.object_id
456 else
457 filter
458 end
459 end
460
23122ab @tenderlove callbacks are wrapped with lambdas
tenderlove authored
461 def conditions_lambdas
db78643 @tenderlove stop keeping a reference to the options hash
tenderlove authored
462 @if.map { |c| make_lambda c } +
463 @unless.map { |c| invert_lambda make_lambda c }
856a4dc @jeremy Refactor filters to use Active Support callbacks. Closes #11235.
jeremy authored
464 end
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
465 end
856a4dc @jeremy Refactor filters to use Active Support callbacks. Closes #11235.
jeremy authored
466
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
467 # An Array with a compile method.
3aee912 @tenderlove use delegation over inheritance so we can figure when to cache / bust…
tenderlove authored
468 class CallbackChain #:nodoc:#
469 include Enumerable
470
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
471 attr_reader :name, :config
472
473 def initialize(name, config)
474 @name = name
475 @config = {
476 :scope => [ :kind ]
bd674fd @vipulnsward change merge to merge!
vipulnsward authored
477 }.merge!(config)
3aee912 @tenderlove use delegation over inheritance so we can figure when to cache / bust…
tenderlove authored
478 @chain = []
ade7d36 @tenderlove cache compiled callbacks
tenderlove authored
479 @callbacks = nil
132db31 @tenderlove make the compile method thread safe
tenderlove authored
480 @mutex = Mutex.new
3aee912 @tenderlove use delegation over inheritance so we can figure when to cache / bust…
tenderlove authored
481 end
482
7820205 @tenderlove fix shadowed variable warnings
tenderlove authored
483 def each(&block); @chain.each(&block); end
484 def index(o); @chain.index(o); end
485 def empty?; @chain.empty?; end
3aee912 @tenderlove use delegation over inheritance so we can figure when to cache / bust…
tenderlove authored
486
ade7d36 @tenderlove cache compiled callbacks
tenderlove authored
487 def insert(index, o)
488 @callbacks = nil
489 @chain.insert(index, o)
490 end
491
492 def delete(o)
493 @callbacks = nil
494 @chain.delete(o)
495 end
496
497 def clear
498 @callbacks = nil
499 @chain.clear
500 self
501 end
502
3aee912 @tenderlove use delegation over inheritance so we can figure when to cache / bust…
tenderlove authored
503 def initialize_copy(other)
ade7d36 @tenderlove cache compiled callbacks
tenderlove authored
504 @callbacks = nil
505 @chain = other.chain.dup
132db31 @tenderlove make the compile method thread safe
tenderlove authored
506 @mutex = Mutex.new
856a4dc @jeremy Refactor filters to use Active Support callbacks. Closes #11235.
jeremy authored
507 end
508
24b75fc @bogdan AS::Callbacks: remove unused code
bogdan authored
509 def compile
132db31 @tenderlove make the compile method thread safe
tenderlove authored
510 @callbacks || @mutex.synchronize do
511 @callbacks ||= @chain.reverse.inject(Filters::ENDING) do |chain, callback|
512 callback.apply chain
513 end
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
514 end
3dc80b7 @bogdan AS::Callbacks::Callback refactor
bogdan authored
515 end
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
516
4a9644a @dmitriy-kiriyenko Prevent callback from being set twice.
dmitriy-kiriyenko authored
517 def append(*callbacks)
518 callbacks.each { |c| append_one(c) }
519 end
520
521 def prepend(*callbacks)
522 callbacks.each { |c| prepend_one(c) }
523 end
524
3aee912 @tenderlove use delegation over inheritance so we can figure when to cache / bust…
tenderlove authored
525 protected
526 def chain; @chain; end
527
4a9644a @dmitriy-kiriyenko Prevent callback from being set twice.
dmitriy-kiriyenko authored
528 private
529
30f297b @jonleighton Revert "Merge pull request #10433 from wangjohn/making_callbacks_more…
jonleighton authored
530 def append_one(callback)
ade7d36 @tenderlove cache compiled callbacks
tenderlove authored
531 @callbacks = nil
30f297b @jonleighton Revert "Merge pull request #10433 from wangjohn/making_callbacks_more…
jonleighton authored
532 remove_duplicates(callback)
3aee912 @tenderlove use delegation over inheritance so we can figure when to cache / bust…
tenderlove authored
533 @chain.push(callback)
30f297b @jonleighton Revert "Merge pull request #10433 from wangjohn/making_callbacks_more…
jonleighton authored
534 end
4a9644a @dmitriy-kiriyenko Prevent callback from being set twice.
dmitriy-kiriyenko authored
535
30f297b @jonleighton Revert "Merge pull request #10433 from wangjohn/making_callbacks_more…
jonleighton authored
536 def prepend_one(callback)
ade7d36 @tenderlove cache compiled callbacks
tenderlove authored
537 @callbacks = nil
30f297b @jonleighton Revert "Merge pull request #10433 from wangjohn/making_callbacks_more…
jonleighton authored
538 remove_duplicates(callback)
3aee912 @tenderlove use delegation over inheritance so we can figure when to cache / bust…
tenderlove authored
539 @chain.unshift(callback)
30f297b @jonleighton Revert "Merge pull request #10433 from wangjohn/making_callbacks_more…
jonleighton authored
540 end
4a9644a @dmitriy-kiriyenko Prevent callback from being set twice.
dmitriy-kiriyenko authored
541
30f297b @jonleighton Revert "Merge pull request #10433 from wangjohn/making_callbacks_more…
jonleighton authored
542 def remove_duplicates(callback)
ade7d36 @tenderlove cache compiled callbacks
tenderlove authored
543 @callbacks = nil
3aee912 @tenderlove use delegation over inheritance so we can figure when to cache / bust…
tenderlove authored
544 @chain.delete_if { |c| callback.duplicates?(c) }
30f297b @jonleighton Revert "Merge pull request #10433 from wangjohn/making_callbacks_more…
jonleighton authored
545 end
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
546 end
aae37bb @jeremy Extract ActiveSupport::Callbacks from Active Record, test case setup …
jeremy authored
547
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
548 module ClassMethods
872e2a8 @vipulnsward "normalize_callback_params" doesn't require name param
vipulnsward authored
549 def normalize_callback_params(filters, block) # :nodoc:
d46cf35 @vipulnsward extract array to a constant
vipulnsward authored
550 type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before
269b463 @gsamokovarov Further clean-up of ActiveSupport::Callbacks
gsamokovarov authored
551 options = filters.extract_options!
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
552 filters.unshift(block) if block
dd03f10 @tenderlove only dup the options once, the Callback object does not mutate them
tenderlove authored
553 [type, filters, options.dup]
35c4a2c @tenderlove extract callback param munging
tenderlove authored
554 end
aae37bb @jeremy Extract ActiveSupport::Callbacks from Active Record, test case setup …
jeremy authored
555
35c4a2c @tenderlove extract callback param munging
tenderlove authored
556 # This is used internally to append, prepend and skip callbacks to the
557 # CallbackChain.
72be280 @tenderlove call extracted method from callback manipulation methods
tenderlove authored
558 def __update_callbacks(name) #:nodoc:
1a3fe8c Prevent callbacks in child classes from being executed more than once.
Rolf Timmermans authored
559 ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse.each do |target|
b93cd60 @tenderlove extract getting an setting callbacks to methods
tenderlove authored
560 chain = target.get_callbacks name
72be280 @tenderlove call extracted method from callback manipulation methods
tenderlove authored
561 yield target, chain.dup
50ec0d9 @josevalim Simplify and optimize callbacks superclass sync.
josevalim authored
562 end
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
563 end
564
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
565 # Install a callback for the given event.
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
566 #
567 # set_callback :save, :before, :before_meth
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
568 # set_callback :save, :after, :after_meth, if: :condition
569 # set_callback :save, :around, ->(r, &block) { stuff; result = block.call; stuff }
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
570 #
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
571 # The second arguments indicates whether the callback is to be run +:before+,
572 # +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
573 # means the first example above can also be written as:
574 #
3c1a0a8 @neerajdotname expand on set_callback method to explain that in
neerajdotname authored
575 # set_callback :save, :before_meth
576 #
fc4b37c @gzohari Fix typo in set_callback docs. [ci skip]
gzohari authored
577 # The callback can be specified as a symbol naming an instance method; as a
019df98 @claudiob Replace comments' non-breaking spaces with spaces
claudiob authored
578 # proc, lambda, or block; as a string to be instance evaluated; or as an
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
579 # object that responds to a certain method determined by the <tt>:scope</tt>
b894b7b @robin850 Fix few typos in the documentation [ci skip]
robin850 authored
580 # argument to +define_callbacks+.
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
581 #
582 # If a proc, lambda, or block is given, its body is evaluated in the context
583 # of the current object. It can also optionally accept the current object as
584 # an argument.
585 #
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
586 # Before and around callbacks are called in the order that they are set;
587 # after callbacks are called in the reverse order.
588 #
df615f1 @obrie Allow access to a callback event's return result from around callbacks
obrie authored
589 # Around callbacks can access the return value from the event, if it
590 # wasn't halted, from the +yield+ call.
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
591 #
592 # ===== Options
593 #
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
594 # * <tt>:if</tt> - A symbol naming an instance method or a proc; the
595 # callback will be called only when it returns a +true+ value.
596 # * <tt>:unless</tt> - A symbol naming an instance method or a proc; the
597 # callback will be called only when it returns a +false+ value.
598 # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
599 # existing chain rather than appended.
9a650a6 @jeremy Silence some trivial warnings: shadowed local vars, indentation misma…
jeremy authored
600 def set_callback(name, *filter_list, &block)
872e2a8 @vipulnsward "normalize_callback_params" doesn't require name param
vipulnsward authored
601 type, filters, options = normalize_callback_params(filter_list, block)
7820205 @tenderlove fix shadowed variable warnings
tenderlove authored
602 self_chain = get_callbacks name
b93cd60 @tenderlove extract getting an setting callbacks to methods
tenderlove authored
603 mapped = filters.map do |filter|
dd03f10 @tenderlove only dup the options once, the Callback object does not mutate them
tenderlove authored
604 Callback.build(self_chain, filter, type, options)
b93cd60 @tenderlove extract getting an setting callbacks to methods
tenderlove authored
605 end
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
606
72be280 @tenderlove call extracted method from callback manipulation methods
tenderlove authored
607 __update_callbacks(name) do |target, chain|
4a9644a @dmitriy-kiriyenko Prevent callback from being set twice.
dmitriy-kiriyenko authored
608 options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
b93cd60 @tenderlove extract getting an setting callbacks to methods
tenderlove authored
609 target.set_callbacks name, chain
aae37bb @jeremy Extract ActiveSupport::Callbacks from Active Record, test case setup …
jeremy authored
610 end
611 end
612
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
613 # Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or
614 # <tt>:unless</tt> options may be passed in order to control when the
615 # callback is skipped.
d0ac56b @neerajdotname adding an example of skipping a callback
neerajdotname authored
616 #
617 # class Writer < Person
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
618 # skip_callback :validate, :before, :check_membership, if: -> { self.age > 18 }
d0ac56b @neerajdotname adding an example of skipping a callback
neerajdotname authored
619 # end
9a650a6 @jeremy Silence some trivial warnings: shadowed local vars, indentation misma…
jeremy authored
620 def skip_callback(name, *filter_list, &block)
872e2a8 @vipulnsward "normalize_callback_params" doesn't require name param
vipulnsward authored
621 type, filters, options = normalize_callback_params(filter_list, block)
72be280 @tenderlove call extracted method from callback manipulation methods
tenderlove authored
622
623 __update_callbacks(name) do |target, chain|
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
624 filters.each do |filter|
625 filter = chain.find {|c| c.matches?(type, filter) }
626
627 if filter && options.any?
929658c @tenderlove push merge code to the callback itself
tenderlove authored
628 new_filter = filter.merge(chain, options)
50fbb74 @josevalim Fix inheritance issue with new callbacks.
josevalim authored
629 chain.insert(chain.index(filter), new_filter)
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
630 end
50fbb74 @josevalim Fix inheritance issue with new callbacks.
josevalim authored
631
632 chain.delete(filter)
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
633 end
b93cd60 @tenderlove extract getting an setting callbacks to methods
tenderlove authored
634 target.set_callbacks name, chain
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
635 end
636 end
637
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
638 # Remove all set callbacks for the given event.
42a3817 @yangchenyun unified the param names across all callbacks manipulation methods
yangchenyun authored
639 def reset_callbacks(name)
640 callbacks = get_callbacks name
50ec0d9 @josevalim Simplify and optimize callbacks superclass sync.
josevalim authored
641
a5dda97 @josevalim Define a convention for descendants and subclasses.
josevalim authored
642 ActiveSupport::DescendantsTracker.descendants(self).each do |target|
42a3817 @yangchenyun unified the param names across all callbacks manipulation methods
yangchenyun authored
643 chain = target.get_callbacks(name).dup
50ec0d9 @josevalim Simplify and optimize callbacks superclass sync.
josevalim authored
644 callbacks.each { |c| chain.delete(c) }
42a3817 @yangchenyun unified the param names across all callbacks manipulation methods
yangchenyun authored
645 target.set_callbacks name, chain
50ec0d9 @josevalim Simplify and optimize callbacks superclass sync.
josevalim authored
646 end
647
42a3817 @yangchenyun unified the param names across all callbacks manipulation methods
yangchenyun authored
648 self.set_callbacks name, callbacks.dup.clear
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
649 end
650
10fffd7 @aayushkhandelwal11 typos rectified lifecycle => life cycle
aayushkhandelwal11 authored
651 # Define sets of events in the object life cycle that support callbacks.
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
652 #
653 # define_callbacks :validate
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
654 # define_callbacks :initialize, :save, :destroy
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
655 #
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
656 # ===== Options
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
657 #
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
658 # * <tt>:terminator</tt> - Determines when a before filter will halt the
659 # callback chain, preventing following callbacks from being called and
49fd826 @yangchenyun updated AS:Callbacks doc for terminator option in define_callbacks me…
yangchenyun authored
660 # the event from being triggered. This should be a lambda to be executed.
661 # The current object and the return result of the callback will be called
662 # with the lambda.
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
663 #
269b463 @gsamokovarov Further clean-up of ActiveSupport::Callbacks
gsamokovarov authored
664 # define_callbacks :validate, terminator: ->(target, result) { result == false }
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
665 #
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
666 # In this example, if any before validate callbacks returns +false+,
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
667 # other callbacks are not executed. Defaults to +false+, meaning no value
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
668 # halts the chain.
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
669 #
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
670 # * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
671 # callbacks should be terminated by the <tt>:terminator</tt> option. By
672 # default after callbacks executed no matter if callback chain was
673 # terminated or not. Option makes sense only when <tt>:terminator</tt>
674 # option is specified.
7661955 @bogdan AS::Callbacks: :skip_after_callbacks_if_terminated option
bogdan authored
675 #
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
676 # * <tt>:scope</tt> - Indicates which methods should be executed when an
677 # object is used as a callback.
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
678 #
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
679 # class Audit
680 # def before(caller)
681 # puts 'Audit: before is called'
682 # end
94de5b8 @fxn edit pass in #define_callbacks rdoc
fxn authored
683 #
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
684 # def before_save(caller)
685 # puts 'Audit: before_save is called'
686 # end
687 # end
ed9a183 @neerajdotname clearer documentation of how scope applies to ActiveSupport callbacks
neerajdotname authored
688 #
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
689 # class Account
690 # include ActiveSupport::Callbacks
94de5b8 @fxn edit pass in #define_callbacks rdoc
fxn authored
691 #
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
692 # define_callbacks :save
693 # set_callback :save, :before, Audit.new
94de5b8 @fxn edit pass in #define_callbacks rdoc
fxn authored
694 #
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
695 # def save
696 # run_callbacks :save do
697 # puts 'save in main'
698 # end
699 # end
700 # end
ed9a183 @neerajdotname clearer documentation of how scope applies to ActiveSupport callbacks
neerajdotname authored
701 #
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
702 # In the above case whenever you save an account the method
703 # <tt>Audit#before</tt> will be called. On the other hand
ed9a183 @neerajdotname clearer documentation of how scope applies to ActiveSupport callbacks
neerajdotname authored
704 #
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
705 # define_callbacks :save, scope: [:kind, :name]
ed9a183 @neerajdotname clearer documentation of how scope applies to ActiveSupport callbacks
neerajdotname authored
706 #
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
707 # would trigger <tt>Audit#before_save</tt> instead. That's constructed
708 # by calling <tt>#{kind}_#{name}</tt> on the given instance. In this
709 # case "kind" is "before" and "name" is "save". In this context +:kind+
710 # and +:name+ have special meanings: +:kind+ refers to the kind of
711 # callback (before/after/around) and +:name+ refers to the method on
712 # which callbacks are being defined.
ed9a183 @neerajdotname clearer documentation of how scope applies to ActiveSupport callbacks
neerajdotname authored
713 #
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
714 # A declaration like
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
715 #
d71d5ba @frodsan update AS docs [ci skip]
frodsan authored
716 # define_callbacks :save, scope: [:name]
e4c8bc1 @neerajdotname adding to the :kind documentation for ActiveSupport callbacks
neerajdotname authored
717 #
726a66a @jfirebaugh Expand and clarify AS::Callbacks docs.
jfirebaugh authored
718 # would call <tt>Audit#save</tt>.
42a3817 @yangchenyun unified the param names across all callbacks manipulation methods
yangchenyun authored
719 def define_callbacks(*names)
269b463 @gsamokovarov Further clean-up of ActiveSupport::Callbacks
gsamokovarov authored
720 options = names.extract_options!
19f842b @senny Revert "remove string based terminators for `ActiveSupport::Callbacks`."
senny authored
721 if options.key?(:terminator) && String === options[:terminator]
722 ActiveSupport::Deprecation.warn "String based terminators are deprecated, please use a lambda"
723 value = options[:terminator]
724 line = class_eval "lambda { |result| #{value} }", __FILE__, __LINE__
725 options[:terminator] = lambda { |target, result| target.instance_exec(result, &line) }
726 end
ba55276 @tenderlove deprecating string based terminators
tenderlove authored
727
42a3817 @yangchenyun unified the param names across all callbacks manipulation methods
yangchenyun authored
728 names.each do |name|
729 class_attribute "_#{name}_callbacks"
269b463 @gsamokovarov Further clean-up of ActiveSupport::Callbacks
gsamokovarov authored
730 set_callbacks name, CallbackChain.new(name, options)
21e7b84 @josh Callbacks, DeprecatedCallbacks = NewCallbacks, Callbacks
josh authored
731 end
732 end
b93cd60 @tenderlove extract getting an setting callbacks to methods
tenderlove authored
733
734 protected
735
736 def get_callbacks(name)
737 send "_#{name}_callbacks"
738 end
739
740 def set_callbacks(name, callbacks)
741 send "_#{name}_callbacks=", callbacks
742 end
aae37bb @jeremy Extract ActiveSupport::Callbacks from Active Record, test case setup …
jeremy authored
743 end
744 end
745 end
Something went wrong with that request. Please try again.