86e66e05883d5f648a6fcbba30db5c7c3888fbf6
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
)
195 [options
[:if]].flatten
.compact
.all
? { |a
| evaluate_method(a
, *args
) } &&
196 ![options
[:unless]].flatten
.compact
.any
? { |a
| evaluate_method(a
, *args
) }
200 def self.included(base
)
201 base
.extend ClassMethods
205 def define_callbacks(*callbacks
)
206 callbacks
.each
do |callback
|
207 class_eval
<<-"end_eval"
208 def self.#{callback}(*methods, &block) # def self.before_save(*methods, &block)
209 callbacks
= CallbackChain
.build(:#{callback}, *methods, &block) # callbacks = CallbackChain.build(:before_save, *methods, &block)
210 @
#{callback}_callbacks ||= CallbackChain.new # @before_save_callbacks ||= CallbackChain.new
211 @
#{callback}_callbacks.concat callbacks # @before_save_callbacks.concat callbacks
214 def self.#{callback}_callback_chain # def self.before_save_callback_chain
215 @
#{callback}_callbacks ||= CallbackChain.new # @before_save_callbacks ||= CallbackChain.new
217 if superclass
.respond_to
?(:#{callback}_callback_chain) # if superclass.respond_to?(:before_save_callback_chain)
218 CallbackChain
.new( # CallbackChain.new(
219 superclass
.#{callback}_callback_chain + # superclass.before_save_callback_chain +
220 @
#{callback}_callbacks # @before_save_callbacks
223 @
#{callback}_callbacks # @before_save_callbacks
231 # Runs all the callbacks defined for the given options.
233 # If a block is given it will be called after each callback receiving as arguments:
235 # * the result from the callback
236 # * the object which has the callback
238 # If the result from the block evaluates to false, the callback chain is stopped.
242 # include ActiveSupport::Callbacks
244 # define_callbacks :before_save, :after_save
247 # class ConfigStorage < Storage
263 # result = run_callbacks(:before_save) { |result, object| result == false }
264 # puts "- save" if result
268 # config = ConfigStorage.new
275 def run_callbacks(kind
, options
= {}, &block
)
276 self.class.send("#{kind}_callback_chain").run(self, options
, &block
)