require 'active_support/core_ext/array/extract_options'
module ActiveModel
# == Active \Model \Callbacks
#
# Provides an interface for any class to have Active Record like callbacks.
#
# Like the Active Record methods, the callback chain is aborted as soon as
# one of the methods in the chain returns +false+.
#
# First, extend ActiveModel::Callbacks from the class you are creating:
#
# class MyModel
# extend ActiveModel::Callbacks
# end
#
# Then define a list of methods that you want callbacks attached to:
#
# define_model_callbacks :create, :update
#
# This will provide all three standard callbacks (before, around and after)
# for both the <tt>:create</tt> and <tt>:update</tt> methods. To implement,
# you need to wrap the methods you want callbacks on in a block so that the
# callbacks get a chance to fire:
#
# def create
# run_callbacks :create do
# # Your create action methods here
# end
# end
#
# Then in your class, you can use the +before_create+, +after_create+ and
# +around_create+ methods, just as you would in an Active Record module.
#
# before_create :action_before_create
#
# def action_before_create
# # Your code here
# end
#
# When defining an around callback remember to yield to the block, otherwise
# it won't be executed:
#
# around_create :log_status
#
# def log_status
# puts 'going to call the block...'
# yield
# puts 'block successfully called.'
# end
#
# You can choose not to have all three callbacks by passing a hash to the
# +define_model_callbacks+ method.
#
# define_model_callbacks :create, only: [:after, :before]
#
# Would only create the +after_create+ and +before_create+ callback methods in
# your class.
module Callbacks
def self.extended(base) #:nodoc:
base.class_eval do
include ActiveSupport::Callbacks
end
end
# define_model_callbacks accepts the same options +define_callbacks+ does,
# in case you want to overwrite a default. Besides that, it also accepts an
# <tt>:only</tt> option, where you can choose if you want all types (before,
# around or after) or just some.
#
# define_model_callbacks :initializer, only: :after
#
# Note, the <tt>only: <type></tt> hash will apply to all callbacks defined
# on that method call. To get around this you can call the define_model_callbacks
# method as many times as you need.
#
# define_model_callbacks :create, only: :after
# define_model_callbacks :update, only: :before
# define_model_callbacks :destroy, only: :around
#
# Would create +after_create+, +before_update+ and +around_destroy+ methods
# only.
#
# You can pass in a class to before_<type>, after_<type> and around_<type>,
# in which case the callback will call that class's <action>_<type> method
# passing the object that the callback is being called on.
#
# class MyModel
# extend ActiveModel::Callbacks
# define_model_callbacks :create
#
# before_create AnotherClass
# end
#
# class AnotherClass
# def self.before_create( obj )
# # obj is the MyModel instance that the callback is being called on
# end
# end
def define_model_callbacks(*callbacks)
options = callbacks.extract_options!
options = {
terminator: "result == false",
skip_after_callbacks_if_terminated: true,
scope: [:kind, :name],
only: [:before, :around, :after]
}.merge!(options)
types = Array(options.delete(:only))
callbacks.each do |callback|
define_callbacks(callback, options)
types.each do |type|
send("_define_#{type}_model_callback", self, callback)
end
end
end
private
def _define_before_model_callback(klass, callback) #:nodoc:
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
def self.before_#{callback}(*args, &block)
set_callback(:#{callback}, :before, *args, &block)
end
CALLBACK
end
def _define_around_model_callback(klass, callback) #:nodoc:
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
def self.around_#{callback}(*args, &block)
set_callback(:#{callback}, :around, *args, &block)
end
CALLBACK
end
def _define_after_model_callback(klass, callback) #:nodoc:
klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
def self.after_#{callback}(*args, &block)
options = args.extract_options!
options[:prepend] = true
options[:if] = Array(options[:if]) << "value != false"
set_callback(:#{callback}, :after, *(args << options), &block)
end
CALLBACK
end
end
end