Merged updates from trunk into stable branch
[feedcatcher.git] / vendor / rails / activesupport / lib / active_support / callbacks.rb
1 module ActiveSupport
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.
4 #
5 # Mixing in this module allows you to define callbacks in your class.
6 #
7 # Example:
8 # class Storage
9 # include ActiveSupport::Callbacks
10 #
11 # define_callbacks :before_save, :after_save
12 # end
13 #
14 # class ConfigStorage < Storage
15 # before_save :saving_message
16 # def saving_message
17 # puts "saving..."
18 # end
19 #
20 # after_save do |object|
21 # puts "saved"
22 # end
23 #
24 # def save
25 # run_callbacks(:before_save)
26 # puts "- save"
27 # run_callbacks(:after_save)
28 # end
29 # end
30 #
31 # config = ConfigStorage.new
32 # config.save
33 #
34 # Output:
35 # saving...
36 # - save
37 # saved
38 #
39 # Callbacks from parent classes are inherited.
40 #
41 # Example:
42 # class Storage
43 # include ActiveSupport::Callbacks
44 #
45 # define_callbacks :before_save, :after_save
46 #
47 # before_save :prepare
48 # def prepare
49 # puts "preparing save"
50 # end
51 # end
52 #
53 # class ConfigStorage < Storage
54 # before_save :saving_message
55 # def saving_message
56 # puts "saving..."
57 # end
58 #
59 # after_save do |object|
60 # puts "saved"
61 # end
62 #
63 # def save
64 # run_callbacks(:before_save)
65 # puts "- save"
66 # run_callbacks(:after_save)
67 # end
68 # end
69 #
70 # config = ConfigStorage.new
71 # config.save
72 #
73 # Output:
74 # preparing save
75 # saving...
76 # - save
77 # saved
78 module Callbacks
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) }
83 new(methods)
84 end
85
86 def run(object, options = {}, &terminator)
87 enumerator = options[:enumerator] || :each
88
89 unless block_given?
90 send(enumerator) { |callback| callback.call(object) }
91 else
92 send(enumerator) do |callback|
93 result = callback.call(object)
94 break result if terminator.call(result, object)
95 end
96 end
97 end
98
99 # TODO: Decompose into more Array like behavior
100 def replace_or_append!(chain)
101 if index = index(chain)
102 self[index] = chain
103 else
104 self << chain
105 end
106 self
107 end
108
109 def find(callback, &block)
110 select { |c| c == callback && (!block_given? || yield(c)) }.first
111 end
112
113 def delete(callback)
114 super(callback.is_a?(Callback) ? callback : find(callback))
115 end
116
117 private
118 def self.extract_options(*methods, &block)
119 methods.flatten!
120 options = methods.extract_options!
121 methods << block if block_given?
122 return methods, options
123 end
124
125 def extract_options(*methods, &block)
126 self.class.extract_options(*methods, &block)
127 end
128 end
129
130 class Callback
131 attr_reader :kind, :method, :identifier, :options
132
133 def initialize(kind, method, options = {})
134 @kind = kind
135 @method = method
136 @identifier = options[:identifier]
137 @options = options
138 end
139
140 def ==(other)
141 case other
142 when Callback
143 (self.identifier && self.identifier == other.identifier) || self.method == other.method
144 else
145 (self.identifier && self.identifier == other) || self.method == other
146 end
147 end
148
149 def eql?(other)
150 self == other
151 end
152
153 def dup
154 self.class.new(@kind, @method, @options.dup)
155 end
156
157 def hash
158 if @identifier
159 @identifier.hash
160 else
161 @method.hash
162 end
163 end
164
165 def call(*args, &block)
166 evaluate_method(method, *args, &block) if should_run_callback?(*args)
167 rescue LocalJumpError
168 raise ArgumentError,
169 "Cannot yield from a Proc type filter. The Proc must take two " +
170 "arguments and execute #call on the second argument."
171 end
172
173 private
174 def evaluate_method(method, *args, &block)
175 case method
176 when Symbol
177 object = args.shift
178 object.send(method, *args, &block)
179 when String
180 eval(method, args.first.instance_eval { binding })
181 when Proc, Method
182 method.call(*args, &block)
183 else
184 if method.respond_to?(kind)
185 method.send(kind, *args, &block)
186 else
187 raise ArgumentError,
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."
190 end
191 end
192 end
193
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) }
197 end
198 end
199
200 def self.included(base)
201 base.extend ClassMethods
202 end
203
204 module 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
212 end # end
213 #
214 def self.#{callback}_callback_chain # def self.before_save_callback_chain
215 @#{callback}_callbacks ||= CallbackChain.new # @before_save_callbacks ||= CallbackChain.new
216 #
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
221 ) # )
222 else # else
223 @#{callback}_callbacks # @before_save_callbacks
224 end # end
225 end # end
226 end_eval
227 end
228 end
229 end
230
231 # Runs all the callbacks defined for the given options.
232 #
233 # If a block is given it will be called after each callback receiving as arguments:
234 #
235 # * the result from the callback
236 # * the object which has the callback
237 #
238 # If the result from the block evaluates to false, the callback chain is stopped.
239 #
240 # Example:
241 # class Storage
242 # include ActiveSupport::Callbacks
243 #
244 # define_callbacks :before_save, :after_save
245 # end
246 #
247 # class ConfigStorage < Storage
248 # before_save :pass
249 # before_save :pass
250 # before_save :stop
251 # before_save :pass
252 #
253 # def pass
254 # puts "pass"
255 # end
256 #
257 # def stop
258 # puts "stop"
259 # return false
260 # end
261 #
262 # def save
263 # result = run_callbacks(:before_save) { |result, object| result == false }
264 # puts "- save" if result
265 # end
266 # end
267 #
268 # config = ConfigStorage.new
269 # config.save
270 #
271 # Output:
272 # pass
273 # pass
274 # stop
275 def run_callbacks(kind, options = {}, &block)
276 self.class.send("#{kind}_callback_chain").run(self, options, &block)
277 end
278 end
279 end