mirror of
https://github.com/github/rails.git
synced 2026-02-10 06:04:55 -05:00
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@8988 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
215 lines
5.4 KiB
Ruby
215 lines
5.4 KiB
Ruby
module ActiveSupport
|
|
# Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
|
|
# before or after an alteration of the object state.
|
|
#
|
|
# Mixing in this module allows you to define callbacks in your class.
|
|
#
|
|
# Example:
|
|
# class Storage
|
|
# include ActiveSupport::Callbacks
|
|
#
|
|
# define_callbacks :before_save, :after_save
|
|
# end
|
|
#
|
|
# class ConfigStorage < Storage
|
|
# before_save :saving_message
|
|
# def saving_message
|
|
# puts "saving..."
|
|
# end
|
|
#
|
|
# after_save do |object|
|
|
# puts "saved"
|
|
# end
|
|
#
|
|
# def save
|
|
# run_callbacks(:before_save)
|
|
# puts "- save"
|
|
# run_callbacks(:after_save)
|
|
# end
|
|
# end
|
|
#
|
|
# config = ConfigStorage.new
|
|
# config.save
|
|
#
|
|
# Output:
|
|
# saving...
|
|
# - save
|
|
# saved
|
|
#
|
|
# Callbacks from parent classes are inherited.
|
|
#
|
|
# Example:
|
|
# class Storage
|
|
# include ActiveSupport::Callbacks
|
|
#
|
|
# define_callbacks :before_save, :after_save
|
|
#
|
|
# before_save :prepare
|
|
# def prepare
|
|
# puts "preparing save"
|
|
# end
|
|
# end
|
|
#
|
|
# class ConfigStorage < Storage
|
|
# before_save :saving_message
|
|
# def saving_message
|
|
# puts "saving..."
|
|
# end
|
|
#
|
|
# after_save do |object|
|
|
# puts "saved"
|
|
# end
|
|
#
|
|
# def save
|
|
# run_callbacks(:before_save)
|
|
# puts "- save"
|
|
# run_callbacks(:after_save)
|
|
# end
|
|
# end
|
|
#
|
|
# config = ConfigStorage.new
|
|
# config.save
|
|
#
|
|
# Output:
|
|
# preparing save
|
|
# saving...
|
|
# - save
|
|
# saved
|
|
module Callbacks
|
|
class Callback
|
|
def self.run(callbacks, object, options = {}, &terminator)
|
|
enumerator = options[:enumerator] || :each
|
|
|
|
unless block_given?
|
|
callbacks.send(enumerator) { |callback| callback.call(object) }
|
|
else
|
|
callbacks.send(enumerator) do |callback|
|
|
result = callback.call(object)
|
|
break result if terminator.call(result, object)
|
|
end
|
|
end
|
|
end
|
|
|
|
attr_reader :kind, :method, :identifier, :options
|
|
|
|
def initialize(kind, method, options = {})
|
|
@kind = kind
|
|
@method = method
|
|
@identifier = options[:identifier]
|
|
@options = options
|
|
end
|
|
|
|
def call(object)
|
|
evaluate_method(method, object) if should_run_callback?(object)
|
|
end
|
|
|
|
private
|
|
def evaluate_method(method, object)
|
|
case method
|
|
when Symbol
|
|
object.send(method)
|
|
when String
|
|
eval(method, object.instance_eval { binding })
|
|
when Proc, Method
|
|
method.call(object)
|
|
else
|
|
if method.respond_to?(kind)
|
|
method.send(kind, object)
|
|
else
|
|
raise ArgumentError,
|
|
"Callbacks must be a symbol denoting the method to call, a string to be evaluated, " +
|
|
"a block to be invoked, or an object responding to the callback method."
|
|
end
|
|
end
|
|
end
|
|
|
|
def should_run_callback?(object)
|
|
if options[:if]
|
|
evaluate_method(options[:if], object)
|
|
elsif options[:unless]
|
|
!evaluate_method(options[:unless], object)
|
|
else
|
|
true
|
|
end
|
|
end
|
|
end
|
|
|
|
def self.included(base)
|
|
base.extend ClassMethods
|
|
end
|
|
|
|
module ClassMethods
|
|
def define_callbacks(*callbacks)
|
|
callbacks.each do |callback|
|
|
class_eval <<-"end_eval"
|
|
def self.#{callback}(*methods, &block)
|
|
options = methods.extract_options!
|
|
methods << block if block_given?
|
|
callbacks = methods.map { |method| Callback.new(:#{callback}, method, options) }
|
|
(@#{callback}_callbacks ||= []).concat callbacks
|
|
end
|
|
|
|
def self.#{callback}_callback_chain
|
|
@#{callback}_callbacks ||= []
|
|
|
|
if superclass.respond_to?(:#{callback}_callback_chain)
|
|
superclass.#{callback}_callback_chain + @#{callback}_callbacks
|
|
else
|
|
@#{callback}_callbacks
|
|
end
|
|
end
|
|
end_eval
|
|
end
|
|
end
|
|
end
|
|
|
|
# Runs all the callbacks defined for the given options.
|
|
#
|
|
# If a block is given it will be called after each callback receiving as arguments:
|
|
#
|
|
# * the result from the callback
|
|
# * the object which has the callback
|
|
#
|
|
# If the result from the block evaluates to false, the callback chain is stopped.
|
|
#
|
|
# Example:
|
|
# class Storage
|
|
# include ActiveSupport::Callbacks
|
|
#
|
|
# define_callbacks :before_save, :after_save
|
|
# end
|
|
#
|
|
# class ConfigStorage < Storage
|
|
# before_save :pass
|
|
# before_save :pass
|
|
# before_save :stop
|
|
# before_save :pass
|
|
#
|
|
# def pass
|
|
# puts "pass"
|
|
# end
|
|
#
|
|
# def stop
|
|
# puts "stop"
|
|
# return false
|
|
# end
|
|
#
|
|
# def save
|
|
# result = run_callbacks(:before_save) { |result, object| result == false }
|
|
# puts "- save" if result
|
|
# end
|
|
# end
|
|
#
|
|
# config = ConfigStorage.new
|
|
# config.save
|
|
#
|
|
# Output:
|
|
# pass
|
|
# pass
|
|
# stop
|
|
def run_callbacks(kind, options = {}, &block)
|
|
Callback.run(self.class.send("#{kind}_callback_chain"), self, options, &block)
|
|
end
|
|
end
|
|
end
|