Skip to content

Commit

Permalink
Expand and clarify AS::Callbacks docs.
Browse files Browse the repository at this point in the history
  • Loading branch information
jfirebaugh committed Jan 9, 2011
1 parent 4d29816 commit 726a66a
Showing 1 changed file with 115 additions and 113 deletions.
228 changes: 115 additions & 113 deletions activesupport/lib/active_support/callbacks.rb
Expand Up @@ -5,27 +5,28 @@
require 'active_support/core_ext/kernel/singleton_class'

module ActiveSupport
# Callbacks are hooks into the life cycle of an object that allow you to trigger logic
# before or after an alteration of the object state.
# \Callbacks are code hooks that are run at key points in an object's lifecycle.
# The typical use case is to have a base class define a set of callbacks relevant
# to the other functionality it supplies, so that subclasses can install callbacks
# that enhance or modify the base functionality without needing to override
# or redefine methods of the base class.
#
# Mixing in this module allows you to define callbacks in your class.
# Mixing in this module allows you to define the events in the object's lifecycle
# that will support callbacks (via +ClassMethods.define_callbacks+), set the instance
# methods, procs, or callback objects to be called (via +ClassMethods.set_callback+),
# and run the installed callbacks at the appropriate times (via +run_callbacks+).
#
# Example:
# class Storage
# include ActiveSupport::Callbacks
# Three kinds of callbacks are supported: before callbacks, run before a certain event;
# after callbacks, run after the event; and around callbacks, blocks that surround the
# event, triggering it when they yield. Callback code can be contained in instance
# methods, procs or lambdas, or callback objects that respond to certain predetermined
# methods. See +ClassMethods.set_callback+ for details.
#
# define_callbacks :save
# end
# ==== Example
#
# class ConfigStorage < Storage
# set_callback :save, :before, :saving_message
# def saving_message
# puts "saving..."
# end
#
# set_callback :save, :after do |object|
# puts "saved"
# end
# class Record
# include ActiveSupport::Callbacks
# define_callbacks :save
#
# def save
# run_callbacks :save do
Expand All @@ -34,29 +35,7 @@ module ActiveSupport
# 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 :save
#
# set_callback :save, :before, :prepare
# def prepare
# puts "preparing save"
# end
# end
#
# class ConfigStorage < Storage
# class PersonRecord < Record
# set_callback :save, :before, :saving_message
# def saving_message
# puts "saving..."
Expand All @@ -65,19 +44,12 @@ module ActiveSupport
# set_callback :save, :after do |object|
# puts "saved"
# end
#
# def save
# run_callbacks :save do
# puts "- save"
# end
# end
# end
#
# config = ConfigStorage.new
# config.save
# person = PersonRecord.new
# person.save
#
# Output:
# preparing save
# saving...
# - save
# saved
Expand All @@ -89,11 +61,25 @@ module Callbacks
extend ActiveSupport::DescendantsTracker
end

# Runs the callbacks for the given event.
#
# Calls the before and around callbacks in the order they were set, yields
# the block (if given one), and then runs the after callbacks in reverse order.
# Optionally accepts a key, which will be used to compile an optimized callback
# method for each key. See +ClassMethods.define_callbacks+ for more information.
#
# If the callback chain was halted, returns +false+. Otherwise returns the result
# of the block, or +true+ if no block is given.
#
# run_callbacks :save do
# save
# end
#
def run_callbacks(kind, *args, &block)
send("_run_#{kind}_callbacks", *args, &block)
end

class Callback
class Callback #:nodoc:#
@@_callback_sequence = 0

attr_accessor :chain, :filter, :kind, :options, :per_key, :klass, :raw_filter
Expand Down Expand Up @@ -328,7 +314,7 @@ def filter.around(context)
end

# An Array with a compile method
class CallbackChain < Array
class CallbackChain < Array #:nodoc:#
attr_reader :name, :config

def initialize(name, config)
Expand Down Expand Up @@ -373,18 +359,7 @@ def compile(key=nil, object=nil)
end

module ClassMethods
# Make the run_callbacks :save method. The generated method takes
# a block that it'll yield to. It'll call the before and around filters
# in order, yield the block, and then run the after filters.
#
# run_callbacks :save do
# save
# end
#
# The run_callbacks :save method can optionally take a key, which
# will be used to compile an optimized callback method for each
# key. See #define_callbacks for more information.
#
# Generate the internal runner method called by +run_callbacks+.
def __define_runner(symbol) #:nodoc:
body = send("_#{symbol}_callbacks").compile

Expand Down Expand Up @@ -440,18 +415,42 @@ def __update_callbacks(name, filters = [], block = nil) #:nodoc:
end
end

# Set callbacks for a previously defined callback.
# Install a callback for the given event.
#
# Syntax:
# set_callback :save, :before, :before_meth
# set_callback :save, :after, :after_meth, :if => :condition
# set_callback :save, :around, lambda { |r| stuff; yield; stuff }
#
# If the second argument is not :before, :after or :around then an implicit :before is assumed.
# It means the first example mentioned above can also be written as:
# The second arguments indicates whether the callback is to be run +:before+,
# +:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
# means the first example above can also be written as:
#
# set_callback :save, :before_meth
#
# Use skip_callback to skip any defined one.
# The callback can specified as a symbol naming an instance method; as a proc,
# lambda, or block; as a string to be instance evaluated; or as an object that
# responds to a certain method determined by the <tt>:scope</tt> argument to
# +define_callback+.
#
# If a proc, lambda, or block is given, its body is evaluated in the context
# of the current object. It can also optionally accept the current object as
# an argument.
#
# Before and around callbacks are called in the order that they are set; after
# callbacks are called in the reverse order.
#
# ===== Options
#
# * <tt>:if</tt> - A symbol naming an instance method or a proc; the callback
# will be called only when it returns a true value.
# * <tt>:unless</tt> - A symbol naming an instance method or a proc; the callback
# will be called only when it returns a false value.
# * <tt>:prepend</tt> - If true, the callback will be prepended to the existing
# chain rather than appended.
# * <tt>:per_key</tt> - A hash with <tt>:if</tt> and <tt>:unless</tt> options;
# see "Per-key conditions" below.
#
# ===== Per-key conditions
#
# When creating or skipping callbacks, you can specify conditions that
# are always the same for a given key. For instance, in Action Pack,
Expand All @@ -463,7 +462,7 @@ def __update_callbacks(name, filters = [], block = nil) #:nodoc:
#
# set_callback :process_action, :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}}
#
# Per-Key conditions are evaluated only once per use of a given key.
# Per-key conditions are evaluated only once per use of a given key.
# In the case of the above example, you would do:
#
# run_callbacks(:process_action, action_name) { ... dispatch stuff ... }
Expand All @@ -490,7 +489,8 @@ def set_callback(name, *filter_list, &block)
end
end

# Skip a previously defined callback.
# Skip a previously set callback. Like +set_callback+, <tt>:if</tt> or <tt>:unless</tt>
# options may be passed in order to control when the callback is skipped.
#
# class Writer < Person
# skip_callback :validate, :before, :check_membership, :if => lambda { self.age > 18 }
Expand All @@ -513,7 +513,7 @@ def skip_callback(name, *filter_list, &block)
end
end

# Reset callbacks for a given type.
# Remove all set callbacks for the given event.
#
def reset_callbacks(symbol)
callbacks = send("_#{symbol}_callbacks")
Expand All @@ -530,68 +530,70 @@ def reset_callbacks(symbol)
__define_runner(symbol)
end

# Defines callbacks types:
# Define sets of events in the object lifecycle that support callbacks.
#
# define_callbacks :validate
# define_callbacks :initialize, :save, :destroy
#
# This macro accepts the following options:
# ===== Options
#
# * <tt>:terminator</tt> - Indicates when a before filter is considered
# to halted. This is a string to be eval'ed and has the result of the
# very filter available in the <tt>result</tt> variable:
# * <tt>:terminator</tt> - Determines when a before filter will halt the callback
# chain, preventing following callbacks from being called and the event from being
# triggered. This is a string to be eval'ed. The result of the callback is available
# in the <tt>result</tt> variable.
#
# define_callbacks :validate, :terminator => "result == false"
# define_callbacks :validate, :terminator => "result == false"
#
# In the example above, if any before validate callbacks returns +false+,
# other callbacks are not executed. Defaults to "false", meaning no value
# halts the chain.
# In this example, if any before validate callbacks returns +false+,
# other callbacks are not executed. Defaults to "false", meaning no value
# halts the chain.
#
# * <tt>:rescuable</tt> - By default, after filters are not executed if
# the given block or a before filter raises an error. Set this option to
# true to change this behavior.
# the given block or a before filter raises an error. Set this option to
# true to change this behavior.
#
# * <tt>:scope</tt> - Indicates which methods should be executed when a class
# is given as callback. Defaults to <tt>[:kind]</tt>.
# * <tt>:scope</tt> - Indicates which methods should be executed when an object
# is used as a callback.
#
# class Audit
# def before(caller)
# puts 'Audit: before is called'
# end
# class Audit
# def before(caller)
# puts 'Audit: before is called'
# end
#
# def before_save(caller)
# puts 'Audit: before_save is called'
# end
# end
# def before_save(caller)
# puts 'Audit: before_save is called'
# end
# end
#
# class Account
# include ActiveSupport::Callbacks
# class Account
# include ActiveSupport::Callbacks
#
# define_callbacks :save
# set_callback :save, :before, Audit.new
# define_callbacks :save
# set_callback :save, :before, Audit.new
#
# def save
# run_callbacks :save do
# puts 'save in main'
# end
# end
# end
# def save
# run_callbacks :save do
# puts 'save in main'
# end
# end
# end
#
# In the above case whenever you save an account the method <tt>Audit#before</tt> will
# be called. On the other hand
# In the above case whenever you save an account the method <tt>Audit#before</tt> will
# be called. On the other hand
#
# define_callbacks :save, :scope => [:kind, :name]
# define_callbacks :save, :scope => [:kind, :name]
#
# would trigger <tt>Audit#before_save</tt> instead. That's constructed by calling
# <tt>"#{kind}_#{name}"</tt> on the given instance. In this case "kind" is "before" and
# "name" is "save". In this context ":kind" and ":name" have special meanings: ":kind"
# refers to the kind of callback (before/after/around) and ":name" refers to the
# method on which callbacks are being defined.
# would trigger <tt>Audit#before_save</tt> instead. That's constructed by calling
# <tt>#{kind}_#{name}</tt> on the given instance. In this case "kind" is "before" and
# "name" is "save". In this context +:kind+ and +:name+ have special meanings: +:kind+
# refers to the kind of callback (before/after/around) and +:name+ refers to the
# method on which callbacks are being defined.
#
# A declaration like
# A declaration like
#
# define_callbacks :save, :scope => [:name]
# define_callbacks :save, :scope => [:name]
#
# would call <tt>Audit#save</tt>.
# would call <tt>Audit#save</tt>.
#
def define_callbacks(*callbacks)
config = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
Expand Down

0 comments on commit 726a66a

Please sign in to comment.