module ActiveSupport::Callbacks::ClassMethods
def __update_callbacks(name) # :nodoc:
CallbackChain.
This is used internally to append, prepend and skip callbacks to the
def __update_callbacks(name) # :nodoc: ([self] + self.descendants).reverse_each do |target| chain = target.get_callbacks name yield target, chain.dup end end
def define_callbacks(*names)
Calling +define_callbacks+ multiple times with the same +names+ will
!, ? or =.
+names+ passed to +define_callbacks+ must not end with
===== Notes
would call Audit#save.
define_callbacks :save, scope: [:name]
A declaration like
which callbacks are being defined.
callback (before/after/around) and +:name+ refers to the method on
and +:name+ have special meanings: +:kind+ refers to the kind of
case "kind" is "before" and "name" is "save". In this context +:kind+
by calling #{kind}_#{name} on the given instance. In this
would trigger Audit#before_save instead. That's constructed
define_callbacks :save, scope: [:kind, :name]
Audit#before will be called. On the other hand
In the above case whenever you save an account the method
end
end
end
puts 'save in main'
run_callbacks :save do
def save
set_callback :save, :before, Audit.new
define_callbacks :save
include ActiveSupport::Callbacks
class Account
end
end
puts 'Audit: before_save is called'
def before_save(caller)
end
puts 'Audit: before is called'
def before(caller)
class Audit
object is used as a callback.
* :scope - Indicates which methods should be executed when an
option is set to +nil+.
terminated or not. This option has no effect if :terminator
default after callbacks are executed no matter if callback chain was
callbacks should be terminated by the :terminator option. By
* :skip_after_callbacks_if_terminated - Determines if after
The default terminator halts the chain when a callback throws +:abort+.
any successive before and around callback is not executed.
In this example, if any before validate callbacks returns +false+,
define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false }
to the terminator lambda.
The current object and the result lambda of the callback will be provided
This should be a lambda to be executed.
being called and the event from being triggered.
callback chain, preventing following before and around callbacks from
* :terminator - Determines when a before filter will halt the
===== Options
define_callbacks :initialize, :save, :destroy
define_callbacks :validate
Define sets of events in the object life cycle that support callbacks.
def define_callbacks(*names) options = names.extract_options! names.each do |name| name = name.to_sym ([self] + self.descendants).each do |target| target.set_callbacks name, CallbackChain.new(name, options) end module_eval <<-RUBY, __FILE__, __LINE__ + 1 def _run_#{name}_callbacks(&block) run_callbacks #{name.inspect}, &block end def self._#{name}_callbacks get_callbacks(#{name.inspect}) end def self._#{name}_callbacks=(value) set_callbacks(#{name.inspect}, value) end def _#{name}_callbacks __callbacks[#{name.inspect}] end RUBY end end
def get_callbacks(name) # :nodoc:
def get_callbacks(name) # :nodoc: __callbacks[name.to_sym] end
def normalize_callback_params(filters, block) # :nodoc:
def normalize_callback_params(filters, block) # :nodoc: type = CALLBACK_FILTER_TYPES.include?(filters.first) ? filters.shift : :before options = filters.extract_options! filters.unshift(block) if block [type, filters, options.dup] end
def reset_callbacks(name)
def reset_callbacks(name) callbacks = get_callbacks name self.descendants.each do |target| chain = target.get_callbacks(name).dup callbacks.each { |c| chain.delete(c) } target.set_callbacks name, chain end set_callbacks(name, callbacks.dup.clear) end
def set_callback(name, *filter_list, &block)
* :prepend - If +true+, the callback will be prepended to the
an argument.
current object. It can also optionally accept the current object as
If a proc is given, its body is evaluated in the context of the
all return a false value.
instance method or a proc; the callback will be called only when they
* :unless - A symbol or an array of symbols, each naming an
an argument.
current object. It can also optionally accept the current object as
If a proc is given, its body is evaluated in the context of the
a true value.
method or a proc; the callback will be called only when they all return
* :if - A symbol or an array of symbols, each naming an instance
===== Options
wasn't halted, from the +yield+ call.
Around callbacks can access the return value from the event, if it
after callbacks are called in the reverse order.
Before and around callbacks are called in the order that they are set;
an argument.
of the current object. It can also optionally accept the current object as
If a proc, lambda, or block is given, its body is evaluated in the context
determined by the :scope argument to +define_callbacks+.
proc, lambda, or block; or as an object that responds to a certain method
The callback can be specified as a symbol naming an instance method; as a
set_callback :save, :before_method
means the first example above can also be written as:
+:after+, or +:around+ the event. If omitted, +:before+ is assumed. This
The second argument indicates whether the callback is to be run +:before+,
set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
set_callback :save, :after, :after_method, if: :condition
set_callback :save, :before, :before_method
Install a callback for the given event.
def set_callback(name, *filter_list, &block) type, filters, options = normalize_callback_params(filter_list, block) self_chain = get_callbacks name mapped = filters.map do |filter| Callback.build(self_chain, filter, type, options) end __update_callbacks(name) do |target, chain| options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped) target.set_callbacks name, chain end end
def set_callbacks(name, callbacks) # :nodoc:
def set_callbacks(name, callbacks) # :nodoc: unless singleton_class.method_defined?(:__callbacks, false) self.__callbacks = __callbacks.dup end self.__callbacks[name.to_sym] = callbacks self.__callbacks end
def skip_callback(name, *filter_list, &block)
An ArgumentError will be raised if the callback has not
saved
- save
saving...
Output:
young_writer.save
young_writer.age = 17
young_writer = Writer.new
When if option returns false, callback is NOT skipped.
saved
- save
Output:
writer.save
writer.age = 20
writer = Writer.new
When if option returns true, callback is skipped.
end
skip_callback :save, :before, :saving_message, if: -> { age > 18 }
attr_accessor :age
class Writer < PersonRecord
callback is skipped.
:unless options may be passed in order to control when the
Skip a previously set callback. Like +set_callback+, :if or
def skip_callback(name, *filter_list, &block) type, filters, options = normalize_callback_params(filter_list, block) options[:raise] = true unless options.key?(:raise) __update_callbacks(name) do |target, chain| filters.each do |filter| callback = chain.find { |c| c.matches?(type, filter) } if !callback && options[:raise] raise ArgumentError, "#{type.to_s.capitalize} #{name} callback #{filter.inspect} has not been defined" end if callback && (options.key?(:if) || options.key?(:unless)) new_callback = callback.merge_conditional_options(chain, if_option: options[:if], unless_option: options[:unless]) chain.insert(chain.index(callback), new_callback) end chain.delete(callback) end target.set_callbacks name, chain end end