Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/trunk/activesupport/lib/active_support/callbacks.rb

Revision 9225, 6.9 kB (checked in by josh, 3 months ago)

Replaced callback method evaluation in AssociationCollection class to use ActiveSupport::Callbacks. Modified ActiveSupport::Callbacks::Callback#call to accept multiple arguments.

Line 
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       def find_callback(callback, &block)
100         select { |c| c == callback && (!block_given? || yield(c)) }.first
101       end
102
103       def replace_or_append_callback(callback)
104         if found_callback = find_callback(callback)
105           index = index(found_callback)
106           self[index] = callback
107         else
108           self << callback
109         end
110       end
111
112       private
113         def self.extract_options(*methods, &block)
114           methods.flatten!
115           options = methods.extract_options!
116           methods << block if block_given?
117           return methods, options
118         end
119
120         def extract_options(*methods, &block)
121           self.class.extract_options(*methods, &block)
122         end
123     end
124
125     class Callback
126       attr_reader :kind, :method, :identifier, :options
127
128       def initialize(kind, method, options = {})
129         @kind       = kind
130         @method     = method
131         @identifier = options[:identifier]
132         @options    = options
133       end
134
135       def ==(other)
136         case other
137         when Callback
138           (self.identifier && self.identifier == other.identifier) || self.method == other.method
139         else
140           (self.identifier && self.identifier == other) || self.method == other
141         end
142       end
143
144       def eql?(other)
145         self == other
146       end
147
148       def dup
149         self.class.new(@kind, @method, @options.dup)
150       end
151
152       def call(*args, &block)
153         evaluate_method(method, *args, &block) if should_run_callback?(*args)
154       rescue LocalJumpError
155         raise ArgumentError,
156           "Cannot yield from a Proc type filter. The Proc must take two " +
157           "arguments and execute #call on the second argument."
158       end
159
160       private
161         def evaluate_method(method, *args, &block)
162           case method
163             when Symbol
164               object = args.shift
165               object.send(method, *args, &block)
166             when String
167               eval(method, args.first.instance_eval { binding })
168             when Proc, Method
169               method.call(*args, &block)
170             else
171               if method.respond_to?(kind)
172                 method.send(kind, *args, &block)
173               else
174                 raise ArgumentError,
175                   "Callbacks must be a symbol denoting the method to call, a string to be evaluated, " +
176                   "a block to be invoked, or an object responding to the callback method."
177               end
178             end
179         end
180
181         def should_run_callback?(*args)
182           if options[:if]
183             evaluate_method(options[:if], *args)
184           elsif options[:unless]
185             !evaluate_method(options[:unless], *args)
186           else
187             true
188           end
189         end
190     end
191
192     def self.included(base)
193       base.extend ClassMethods
194     end
195
196     module ClassMethods
197       def define_callbacks(*callbacks)
198         callbacks.each do |callback|
199           class_eval <<-"end_eval"
200             def self.#{callback}(*methods, &block)
201               callbacks = CallbackChain.build(:#{callback}, *methods, &block)
202               (@#{callback}_callbacks ||= CallbackChain.new).concat callbacks
203             end
204
205             def self.#{callback}_callback_chain
206               @#{callback}_callbacks ||= CallbackChain.new
207
208               if superclass.respond_to?(:#{callback}_callback_chain)
209                 CallbackChain.new(superclass.#{callback}_callback_chain + @#{callback}_callbacks)
210               else
211                 @#{callback}_callbacks
212               end
213             end
214           end_eval
215         end
216       end
217     end
218
219     # Runs all the callbacks defined for the given options.
220     #
221     # If a block is given it will be called after each callback receiving as arguments:
222     #
223     #  * the result from the callback
224     #  * the object which has the callback
225     #
226     # If the result from the block evaluates to false, the callback chain is stopped.
227     #
228     # Example:
229     #   class Storage
230     #     include ActiveSupport::Callbacks
231     #   
232     #     define_callbacks :before_save, :after_save
233     #   end
234     #   
235     #   class ConfigStorage < Storage
236     #     before_save :pass
237     #     before_save :pass
238     #     before_save :stop
239     #     before_save :pass
240     #   
241     #     def pass
242     #       puts "pass"
243     #     end
244     #   
245     #     def stop
246     #       puts "stop"
247     #       return false
248     #     end
249     #   
250     #     def save
251     #       result = run_callbacks(:before_save) { |result, object| result == false }
252     #       puts "- save" if result
253     #     end
254     #   end
255     #   
256     #   config = ConfigStorage.new
257     #   config.save
258     #
259     # Output:
260     #   pass
261     #   pass
262     #   stop
263     def run_callbacks(kind, options = {}, &block)
264       self.class.send("#{kind}_callback_chain").run(self, options, &block)
265     end
266   end
267 end
Note: See TracBrowser for help on using the browser.