Froze rails gems
[depot.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 if options[:if]
196 evaluate_method(options[:if], *args)
197 elsif options[:unless]
198 !evaluate_method(options[:unless], *args)
199 else
200 true
201 end
202 end
203 end
204
205 def self.included(base)
206 base.extend ClassMethods
207 end
208
209 module 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
216 end
217
218 def self.#{callback}_callback_chain
219 @#{callback}_callbacks ||= CallbackChain.new
220
221 if superclass.respond_to?(:#{callback}_callback_chain)
222 CallbackChain.new(superclass.#{callback}_callback_chain + @#{callback}_callbacks)
223 else
224 @#{callback}_callbacks
225 end
226 end
227 end_eval
228 end
229 end
230 end
231
232 # Runs all the callbacks defined for the given options.
233 #
234 # If a block is given it will be called after each callback receiving as arguments:
235 #
236 # * the result from the callback
237 # * the object which has the callback
238 #
239 # If the result from the block evaluates to false, the callback chain is stopped.
240 #
241 # Example:
242 # class Storage
243 # include ActiveSupport::Callbacks
244 #
245 # define_callbacks :before_save, :after_save
246 # end
247 #
248 # class ConfigStorage < Storage
249 # before_save :pass
250 # before_save :pass
251 # before_save :stop
252 # before_save :pass
253 #
254 # def pass
255 # puts "pass"
256 # end
257 #
258 # def stop
259 # puts "stop"
260 # return false
261 # end
262 #
263 # def save
264 # result = run_callbacks(:before_save) { |result, object| result == false }
265 # puts "- save" if result
266 # end
267 # end
268 #
269 # config = ConfigStorage.new
270 # config.save
271 #
272 # Output:
273 # pass
274 # pass
275 # stop
276 def run_callbacks(kind, options = {}, &block)
277 self.class.send("#{kind}_callback_chain").run(self, options, &block)
278 end
279 end
280 end