2 # Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
3 # before or after an alteration of the object state.
5 # Mixing in this module allows you to define callbacks in your class.
9 # include ActiveSupport::Callbacks
11 # define_callbacks :before_save, :after_save
14 # class ConfigStorage < Storage
15 # before_save :saving_message
20 # after_save do |object|
25 # run_callbacks(:before_save)
27 # run_callbacks(:after_save)
31 # config = ConfigStorage.new
39 # Callbacks from parent classes are inherited.
43 # include ActiveSupport::Callbacks
45 # define_callbacks :before_save, :after_save
47 # before_save :prepare
49 # puts "preparing save"
53 # class ConfigStorage < Storage
54 # before_save :saving_message
59 # after_save do |object|
64 # run_callbacks(:before_save)
66 # run_callbacks(:after_save)
70 # config = ConfigStorage.new
79 class CallbackChain
< Array
80 def self.build(kind
, *methods
, &block
)
81 methods
, options
= extract_options(*methods
, &block
)
82 methods
.map
! { |method
| Callback
.new(kind
, method
, options
) }
86 def run(object
, options
= {}, &terminator
)
87 enumerator
= options
[:enumerator] || :each
90 send(enumerator
) { |callback
| callback
.call(object
) }
92 send(enumerator
) do |callback
|
93 result
= callback
.call(object
)
94 break result
if terminator
.call(result
, object
)
99 # TODO: Decompose into more Array like behavior
100 def replace_or_append
!(chain
)
101 if index
= index(chain
)
109 def find(callback
, &block
)
110 select
{ |c
| c
== callback
&& (!block_given
? || yield(c
)) }.first
114 super(callback
.is_a
?(Callback
) ? callback
: find(callback
))
118 def self.extract_options(*methods
, &block
)
120 options
= methods
.extract_options
!
121 methods
<< block
if block_given
?
122 return methods
, options
125 def extract_options(*methods
, &block
)
126 self.class.extract_options(*methods
, &block
)
131 attr_reader
:kind, :method, :identifier, :options
133 def initialize(kind
, method
, options
= {})
136 @identifier = options
[:identifier]
143 (self.identifier
&& self.identifier
== other
.identifier
) || self.method
== other
.method
145 (self.identifier
&& self.identifier
== other
) || self.method
== other
154 self.class.new(@kind, @method, @options.dup
)
165 def call(*args
, &block
)
166 evaluate_method(method
, *args
, &block
) if should_run_callback
?(*args
)
167 rescue LocalJumpError
169 "Cannot yield from a Proc type filter. The Proc must take two " +
170 "arguments and execute #call on the second argument."
174 def evaluate_method(method
, *args
, &block
)
178 object
.send(method
, *args
, &block
)
180 eval(method
, args
.first
.instance_eval
{ binding
})
182 method
.call(*args
, &block
)
184 if method
.respond_to
?(kind
)
185 method
.send(kind
, *args
, &block
)
188 "Callbacks must be a symbol denoting the method to call, a string to be evaluated, " +
189 "a block to be invoked, or an object responding to the callback method."
194 def should_run_callback
?(*args
)
196 evaluate_method(options
[:if], *args
)
197 elsif options
[:unless]
198 !evaluate_method(options
[:unless], *args
)
205 def self.included(base
)
206 base
.extend ClassMethods
210 def define_callbacks(*callbacks
)
211 callbacks
.each
do |callback
|
212 class_eval
<<-"end_eval"
213 def self.#{callback}(*methods, &block)
214 callbacks
= CallbackChain
.build(:#{callback}, *methods, &block)
215 (@
#{callback}_callbacks ||= CallbackChain.new).concat callbacks
218 def self.#{callback}_callback_chain
219 @
#{callback}_callbacks ||= CallbackChain.new
221 if superclass
.respond_to
?(:#{callback}_callback_chain)
222 CallbackChain
.new(superclass
.#{callback}_callback_chain + @#{callback}_callbacks)
224 @
#{callback}_callbacks
232 # Runs all the callbacks defined for the given options.
234 # If a block is given it will be called after each callback receiving as arguments:
236 # * the result from the callback
237 # * the object which has the callback
239 # If the result from the block evaluates to false, the callback chain is stopped.
243 # include ActiveSupport::Callbacks
245 # define_callbacks :before_save, :after_save
248 # class ConfigStorage < Storage
264 # result = run_callbacks(:before_save) { |result, object| result == false }
265 # puts "- save" if result
269 # config = ConfigStorage.new
276 def run_callbacks(kind
, options
= {}, &block
)
277 self.class.send("#{kind}_callback_chain").run(self, options
, &block
)