aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport/lib/active_support/callbacks.rb
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport/lib/active_support/callbacks.rb')
-rw-r--r--activesupport/lib/active_support/callbacks.rb1120
1 files changed, 560 insertions, 560 deletions
diff --git a/activesupport/lib/active_support/callbacks.rb b/activesupport/lib/active_support/callbacks.rb
index 985f432c7b..a450b0e6d4 100644
--- a/activesupport/lib/active_support/callbacks.rb
+++ b/activesupport/lib/active_support/callbacks.rb
@@ -92,705 +92,705 @@ module ActiveSupport
private
- def __run_callbacks__(callbacks, &block)
- if callbacks.empty?
- yield if block_given?
- else
- runner = callbacks.compile
- e = Filters::Environment.new(self, false, nil, block)
- runner.call(e).value
+ def __run_callbacks__(callbacks, &block)
+ if callbacks.empty?
+ yield if block_given?
+ else
+ runner = callbacks.compile
+ e = Filters::Environment.new(self, false, nil, block)
+ runner.call(e).value
+ end
end
- end
# A hook invoked every time a before callback is halted.
# This can be overridden in ActiveSupport::Callbacks implementors in order
# to provide better debugging/logging.
- def halted_callback_hook(filter)
- end
-
- module Conditionals # :nodoc:
- class Value
- def initialize(&block)
- @block = block
- end
- def call(target, value); @block.call(value); end
+ def halted_callback_hook(filter)
end
- end
-
- module Filters
- Environment = Struct.new(:target, :halted, :value, :run_block)
- class End
- def call(env)
- block = env.run_block
- env.value = !env.halted && (!block || block.call)
- env
+ module Conditionals # :nodoc:
+ class Value
+ def initialize(&block)
+ @block = block
+ end
+ def call(target, value); @block.call(value); end
end
end
- ENDING = End.new
- class Before
- def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter)
- halted_lambda = chain_config[:terminator]
+ module Filters
+ Environment = Struct.new(:target, :halted, :value, :run_block)
- if user_conditions.any?
- halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
- else
- halting(callback_sequence, user_callback, halted_lambda, filter)
+ class End
+ def call(env)
+ block = env.run_block
+ env.value = !env.halted && (!block || block.call)
+ env
end
end
+ ENDING = End.new
- def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
- callback_sequence.before do |env|
- target = env.target
- value = env.value
- halted = env.halted
+ class Before
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config, filter)
+ halted_lambda = chain_config[:terminator]
- if !halted && user_conditions.all? { |c| c.call(target, value) }
- result_lambda = -> { user_callback.call target, value }
- env.halted = halted_lambda.call(target, result_lambda)
- if env.halted
- target.send :halted_callback_hook, filter
- end
+ if user_conditions.any?
+ halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
+ else
+ halting(callback_sequence, user_callback, halted_lambda, filter)
end
+ end
- env
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions, halted_lambda, filter)
+ callback_sequence.before do |env|
+ target = env.target
+ value = env.value
+ halted = env.halted
+
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
+ result_lambda = -> { user_callback.call target, value }
+ env.halted = halted_lambda.call(target, result_lambda)
+ if env.halted
+ target.send :halted_callback_hook, filter
+ end
+ end
+
+ env
+ end
end
- end
- private_class_method :halting_and_conditional
+ private_class_method :halting_and_conditional
- def self.halting(callback_sequence, user_callback, halted_lambda, filter)
- callback_sequence.before do |env|
- target = env.target
- value = env.value
- halted = env.halted
+ def self.halting(callback_sequence, user_callback, halted_lambda, filter)
+ callback_sequence.before do |env|
+ target = env.target
+ value = env.value
+ halted = env.halted
- unless halted
- result_lambda = -> { user_callback.call target, value }
- env.halted = halted_lambda.call(target, result_lambda)
+ unless halted
+ result_lambda = -> { user_callback.call target, value }
+ env.halted = halted_lambda.call(target, result_lambda)
- if env.halted
- target.send :halted_callback_hook, filter
+ if env.halted
+ target.send :halted_callback_hook, filter
+ end
end
- end
- env
+ env
+ end
end
+ private_class_method :halting
end
- private_class_method :halting
- end
- class After
- def self.build(callback_sequence, user_callback, user_conditions, chain_config)
- if chain_config[:skip_after_callbacks_if_terminated]
- if user_conditions.any?
- halting_and_conditional(callback_sequence, user_callback, user_conditions)
- else
- halting(callback_sequence, user_callback)
- end
- else
- if user_conditions.any?
- conditional callback_sequence, user_callback, user_conditions
+ class After
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config)
+ if chain_config[:skip_after_callbacks_if_terminated]
+ if user_conditions.any?
+ halting_and_conditional(callback_sequence, user_callback, user_conditions)
+ else
+ halting(callback_sequence, user_callback)
+ end
else
- simple callback_sequence, user_callback
+ if user_conditions.any?
+ conditional callback_sequence, user_callback, user_conditions
+ else
+ simple callback_sequence, user_callback
+ end
end
end
- end
- def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
- callback_sequence.after do |env|
- target = env.target
- value = env.value
- halted = env.halted
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.after do |env|
+ target = env.target
+ value = env.value
+ halted = env.halted
- if !halted && user_conditions.all? { |c| c.call(target, value) }
- user_callback.call target, value
- end
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
+ user_callback.call target, value
+ end
- env
+ env
+ end
end
- end
- private_class_method :halting_and_conditional
+ private_class_method :halting_and_conditional
- def self.halting(callback_sequence, user_callback)
- callback_sequence.after do |env|
- unless env.halted
- user_callback.call env.target, env.value
- end
+ def self.halting(callback_sequence, user_callback)
+ callback_sequence.after do |env|
+ unless env.halted
+ user_callback.call env.target, env.value
+ end
- env
+ env
+ end
end
- end
- private_class_method :halting
+ private_class_method :halting
- def self.conditional(callback_sequence, user_callback, user_conditions)
- callback_sequence.after do |env|
- target = env.target
- value = env.value
+ def self.conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.after do |env|
+ target = env.target
+ value = env.value
- if user_conditions.all? { |c| c.call(target, value) }
- user_callback.call target, value
- end
+ if user_conditions.all? { |c| c.call(target, value) }
+ user_callback.call target, value
+ end
- env
+ env
+ end
end
- end
- private_class_method :conditional
+ private_class_method :conditional
- def self.simple(callback_sequence, user_callback)
- callback_sequence.after do |env|
- user_callback.call env.target, env.value
+ def self.simple(callback_sequence, user_callback)
+ callback_sequence.after do |env|
+ user_callback.call env.target, env.value
- env
+ env
+ end
end
+ private_class_method :simple
end
- private_class_method :simple
- end
- class Around
- def self.build(callback_sequence, user_callback, user_conditions, chain_config)
- if user_conditions.any?
- halting_and_conditional(callback_sequence, user_callback, user_conditions)
- else
- halting(callback_sequence, user_callback)
+ class Around
+ def self.build(callback_sequence, user_callback, user_conditions, chain_config)
+ if user_conditions.any?
+ halting_and_conditional(callback_sequence, user_callback, user_conditions)
+ else
+ halting(callback_sequence, user_callback)
+ end
end
- end
- def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
- callback_sequence.around do |env, &run|
- target = env.target
- value = env.value
- halted = env.halted
-
- if !halted && user_conditions.all? { |c| c.call(target, value) }
- user_callback.call(target, value) {
- run.call.value
- }
- env
- else
- run.call
+ def self.halting_and_conditional(callback_sequence, user_callback, user_conditions)
+ callback_sequence.around do |env, &run|
+ target = env.target
+ value = env.value
+ halted = env.halted
+
+ if !halted && user_conditions.all? { |c| c.call(target, value) }
+ user_callback.call(target, value) {
+ run.call.value
+ }
+ env
+ else
+ run.call
+ end
end
end
- end
- private_class_method :halting_and_conditional
+ private_class_method :halting_and_conditional
- def self.halting(callback_sequence, user_callback)
- callback_sequence.around do |env, &run|
- target = env.target
- value = env.value
+ def self.halting(callback_sequence, user_callback)
+ callback_sequence.around do |env, &run|
+ target = env.target
+ value = env.value
- if env.halted
- run.call
- else
- user_callback.call(target, value) {
- run.call.value
- }
- env
+ if env.halted
+ run.call
+ else
+ user_callback.call(target, value) {
+ run.call.value
+ }
+ env
+ end
end
end
+ private_class_method :halting
end
- private_class_method :halting
end
- end
- class Callback #:nodoc:#
- def self.build(chain, filter, kind, options)
- if filter.is_a?(String)
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ class Callback #:nodoc:#
+ def self.build(chain, filter, kind, options)
+ if filter.is_a?(String)
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
Passing string to define callback is deprecated and will be removed
in Rails 5.1 without replacement.
MSG
- end
-
- new chain.name, filter, kind, options, chain.config
- end
+ end
- attr_accessor :kind, :name
- attr_reader :chain_config
-
- def initialize(name, filter, kind, options, chain_config)
- @chain_config = chain_config
- @name = name
- @kind = kind
- @filter = filter
- @key = compute_identifier filter
- @if = Array(options[:if])
- @unless = Array(options[:unless])
- end
+ new chain.name, filter, kind, options, chain.config
+ end
- def filter; @key; end
- def raw_filter; @filter; end
+ attr_accessor :kind, :name
+ attr_reader :chain_config
+
+ def initialize(name, filter, kind, options, chain_config)
+ @chain_config = chain_config
+ @name = name
+ @kind = kind
+ @filter = filter
+ @key = compute_identifier filter
+ @if = Array(options[:if])
+ @unless = Array(options[:unless])
+ end
- def merge_conditional_options(chain, if_option:, unless_option:)
- options = {
- if: @if.dup,
- unless: @unless.dup
- }
+ def filter; @key; end
+ def raw_filter; @filter; end
- options[:if].concat Array(unless_option)
- options[:unless].concat Array(if_option)
+ def merge_conditional_options(chain, if_option:, unless_option:)
+ options = {
+ if: @if.dup,
+ unless: @unless.dup
+ }
- self.class.build chain, @filter, @kind, options
- end
+ options[:if].concat Array(unless_option)
+ options[:unless].concat Array(if_option)
- def matches?(_kind, _filter)
- @kind == _kind && filter == _filter
- end
+ self.class.build chain, @filter, @kind, options
+ end
- def duplicates?(other)
- case @filter
- when Symbol, String
- matches?(other.kind, other.filter)
- else
- false
+ def matches?(_kind, _filter)
+ @kind == _kind && filter == _filter
end
- end
- # Wraps code with filter
- def apply(callback_sequence)
- user_conditions = conditions_lambdas
- user_callback = make_lambda @filter
-
- case kind
- when :before
- Filters::Before.build(callback_sequence, user_callback, user_conditions, chain_config, @filter)
- when :after
- Filters::After.build(callback_sequence, user_callback, user_conditions, chain_config)
- when :around
- Filters::Around.build(callback_sequence, user_callback, user_conditions, chain_config)
+ def duplicates?(other)
+ case @filter
+ when Symbol, String
+ matches?(other.kind, other.filter)
+ else
+ false
+ end
end
- end
- private
+ # Wraps code with filter
+ def apply(callback_sequence)
+ user_conditions = conditions_lambdas
+ user_callback = make_lambda @filter
+
+ case kind
+ when :before
+ Filters::Before.build(callback_sequence, user_callback, user_conditions, chain_config, @filter)
+ when :after
+ Filters::After.build(callback_sequence, user_callback, user_conditions, chain_config)
+ when :around
+ Filters::Around.build(callback_sequence, user_callback, user_conditions, chain_config)
+ end
+ end
- def invert_lambda(l)
- lambda { |*args, &blk| !l.call(*args, &blk) }
- end
+ private
- # Filters support:
- #
- # Symbols:: A method to call.
- # Strings:: Some content to evaluate.
- # Procs:: A proc to call with the object.
- # Objects:: An object with a <tt>before_foo</tt> method on it to call.
- #
- # All of these objects are converted into a lambda and handled
- # the same after this point.
- def make_lambda(filter)
- case filter
- when Symbol
- lambda { |target, _, &blk| target.send filter, &blk }
- when String
- l = eval "lambda { |value| #{filter} }"
- lambda { |target, value| target.instance_exec(value, &l) }
- when Conditionals::Value then filter
- when ::Proc
- if filter.arity > 1
- return lambda { |target, _, &block|
- raise ArgumentError unless block
- target.instance_exec(target, block, &filter)
- }
+ def invert_lambda(l)
+ lambda { |*args, &blk| !l.call(*args, &blk) }
end
- if filter.arity <= 0
- lambda { |target, _| target.instance_exec(&filter) }
- else
- lambda { |target, _| target.instance_exec(target, &filter) }
- end
- else
- scopes = Array(chain_config[:scope])
- method_to_call = scopes.map{ |s| public_send(s) }.join("_")
+ # Filters support:
+ #
+ # Symbols:: A method to call.
+ # Strings:: Some content to evaluate.
+ # Procs:: A proc to call with the object.
+ # Objects:: An object with a <tt>before_foo</tt> method on it to call.
+ #
+ # All of these objects are converted into a lambda and handled
+ # the same after this point.
+ def make_lambda(filter)
+ case filter
+ when Symbol
+ lambda { |target, _, &blk| target.send filter, &blk }
+ when String
+ l = eval "lambda { |value| #{filter} }"
+ lambda { |target, value| target.instance_exec(value, &l) }
+ when Conditionals::Value then filter
+ when ::Proc
+ if filter.arity > 1
+ return lambda { |target, _, &block|
+ raise ArgumentError unless block
+ target.instance_exec(target, block, &filter)
+ }
+ end
- lambda { |target, _, &blk|
- filter.public_send method_to_call, target, &blk
- }
- end
- end
+ if filter.arity <= 0
+ lambda { |target, _| target.instance_exec(&filter) }
+ else
+ lambda { |target, _| target.instance_exec(target, &filter) }
+ end
+ else
+ scopes = Array(chain_config[:scope])
+ method_to_call = scopes.map{ |s| public_send(s) }.join("_")
- def compute_identifier(filter)
- case filter
- when String, ::Proc
- filter.object_id
- else
- filter
- end
- end
+ lambda { |target, _, &blk|
+ filter.public_send method_to_call, target, &blk
+ }
+ end
+ end
+
+ def compute_identifier(filter)
+ case filter
+ when String, ::Proc
+ filter.object_id
+ else
+ filter
+ end
+ end
- def conditions_lambdas
- @if.map { |c| make_lambda c } +
- @unless.map { |c| invert_lambda make_lambda c }
+ def conditions_lambdas
+ @if.map { |c| make_lambda c } +
+ @unless.map { |c| invert_lambda make_lambda c }
+ end
end
- end
# Execute before and after filters in a sequence instead of
# chaining them with nested lambda calls, see:
# https://github.com/rails/rails/issues/18011
- class CallbackSequence
- def initialize(&call)
- @call = call
- @before = []
- @after = []
- end
+ class CallbackSequence
+ def initialize(&call)
+ @call = call
+ @before = []
+ @after = []
+ end
- def before(&before)
- @before.unshift(before)
- self
- end
+ def before(&before)
+ @before.unshift(before)
+ self
+ end
- def after(&after)
- @after.push(after)
- self
- end
+ def after(&after)
+ @after.push(after)
+ self
+ end
- def around(&around)
- CallbackSequence.new do |arg|
- around.call(arg) {
- self.call(arg)
- }
+ def around(&around)
+ CallbackSequence.new do |arg|
+ around.call(arg) {
+ self.call(arg)
+ }
+ end
end
- end
- def call(arg)
- @before.each { |b| b.call(arg) }
- value = @call.call(arg)
- @after.each { |a| a.call(arg) }
- value
+ def call(arg)
+ @before.each { |b| b.call(arg) }
+ value = @call.call(arg)
+ @after.each { |a| a.call(arg) }
+ value
+ end
end
- end
# An Array with a compile method.
- class CallbackChain #:nodoc:#
- include Enumerable
-
- attr_reader :name, :config
-
- def initialize(name, config)
- @name = name
- @config = {
- scope: [:kind],
- terminator: default_terminator
- }.merge!(config)
- @chain = []
- @callbacks = nil
- @mutex = Mutex.new
- end
+ class CallbackChain #:nodoc:#
+ include Enumerable
+
+ attr_reader :name, :config
+
+ def initialize(name, config)
+ @name = name
+ @config = {
+ scope: [:kind],
+ terminator: default_terminator
+ }.merge!(config)
+ @chain = []
+ @callbacks = nil
+ @mutex = Mutex.new
+ end
- def each(&block); @chain.each(&block); end
- def index(o); @chain.index(o); end
- def empty?; @chain.empty?; end
+ def each(&block); @chain.each(&block); end
+ def index(o); @chain.index(o); end
+ def empty?; @chain.empty?; end
- def insert(index, o)
- @callbacks = nil
- @chain.insert(index, o)
- end
+ def insert(index, o)
+ @callbacks = nil
+ @chain.insert(index, o)
+ end
- def delete(o)
- @callbacks = nil
- @chain.delete(o)
- end
+ def delete(o)
+ @callbacks = nil
+ @chain.delete(o)
+ end
- def clear
- @callbacks = nil
- @chain.clear
- self
- end
+ def clear
+ @callbacks = nil
+ @chain.clear
+ self
+ end
- def initialize_copy(other)
- @callbacks = nil
- @chain = other.chain.dup
- @mutex = Mutex.new
- end
+ def initialize_copy(other)
+ @callbacks = nil
+ @chain = other.chain.dup
+ @mutex = Mutex.new
+ end
- def compile
- @callbacks || @mutex.synchronize do
- final_sequence = CallbackSequence.new { |env| Filters::ENDING.call(env) }
- @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
- callback.apply callback_sequence
+ def compile
+ @callbacks || @mutex.synchronize do
+ final_sequence = CallbackSequence.new { |env| Filters::ENDING.call(env) }
+ @callbacks ||= @chain.reverse.inject(final_sequence) do |callback_sequence, callback|
+ callback.apply callback_sequence
+ end
end
end
- end
- def append(*callbacks)
- callbacks.each { |c| append_one(c) }
- end
-
- def prepend(*callbacks)
- callbacks.each { |c| prepend_one(c) }
- end
+ def append(*callbacks)
+ callbacks.each { |c| append_one(c) }
+ end
- protected
- def chain; @chain; end
+ def prepend(*callbacks)
+ callbacks.each { |c| prepend_one(c) }
+ end
- private
+ protected
+ def chain; @chain; end
- def append_one(callback)
- @callbacks = nil
- remove_duplicates(callback)
- @chain.push(callback)
- end
+ private
- def prepend_one(callback)
- @callbacks = nil
- remove_duplicates(callback)
- @chain.unshift(callback)
- end
+ def append_one(callback)
+ @callbacks = nil
+ remove_duplicates(callback)
+ @chain.push(callback)
+ end
- def remove_duplicates(callback)
- @callbacks = nil
- @chain.delete_if { |c| callback.duplicates?(c) }
- end
+ def prepend_one(callback)
+ @callbacks = nil
+ remove_duplicates(callback)
+ @chain.unshift(callback)
+ end
- def default_terminator
- Proc.new do |target, result_lambda|
- terminate = true
- catch(:abort) do
- result_lambda.call if result_lambda.is_a?(Proc)
- terminate = false
+ def remove_duplicates(callback)
+ @callbacks = nil
+ @chain.delete_if { |c| callback.duplicates?(c) }
end
- terminate
- end
- end
- end
- module ClassMethods
- 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]
+ def default_terminator
+ Proc.new do |target, result_lambda|
+ terminate = true
+ catch(:abort) do
+ result_lambda.call if result_lambda.is_a?(Proc)
+ terminate = false
+ end
+ terminate
+ end
+ end
end
- # This is used internally to append, prepend and skip callbacks to the
- # CallbackChain.
- def __update_callbacks(name) #:nodoc:
- ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse_each do |target|
- chain = target.get_callbacks name
- yield target, chain.dup
+ module ClassMethods
+ 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
- end
- # Install a callback for the given event.
- #
- # set_callback :save, :before, :before_method
- # set_callback :save, :after, :after_method, if: :condition
- # set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
- #
- # The second argument 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_method
- #
- # The callback can be specified as a symbol naming an instance method; as a
- # proc, lambda, or block; as a string to be instance evaluated(deprecated); or as an
- # object that responds to a certain method determined by the <tt>:scope</tt>
- # argument to +define_callbacks+.
- #
- # 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.
- #
- # Around callbacks can access the return value from the event, if it
- # wasn't halted, from the +yield+ call.
- #
- # ===== Options
- #
- # * <tt>:if</tt> - A symbol, a string or an array of symbols and strings,
- # each naming an instance method or a proc; the callback will be called
- # only when they all return a true value.
- # * <tt>:unless</tt> - A symbol, a string or an array of symbols and
- # strings, each naming an instance method or a proc; the callback will
- # be called only when they all return a false value.
- # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
- # existing chain rather than appended.
- 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)
+ # This is used internally to append, prepend and skip callbacks to the
+ # CallbackChain.
+ def __update_callbacks(name) #:nodoc:
+ ([self] + ActiveSupport::DescendantsTracker.descendants(self)).reverse_each do |target|
+ chain = target.get_callbacks name
+ yield target, chain.dup
+ end
end
- __update_callbacks(name) do |target, chain|
- options[:prepend] ? chain.prepend(*mapped) : chain.append(*mapped)
- target.set_callbacks name, chain
+ # Install a callback for the given event.
+ #
+ # set_callback :save, :before, :before_method
+ # set_callback :save, :after, :after_method, if: :condition
+ # set_callback :save, :around, ->(r, block) { stuff; result = block.call; stuff }
+ #
+ # The second argument 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_method
+ #
+ # The callback can be specified as a symbol naming an instance method; as a
+ # proc, lambda, or block; as a string to be instance evaluated(deprecated); or as an
+ # object that responds to a certain method determined by the <tt>:scope</tt>
+ # argument to +define_callbacks+.
+ #
+ # 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.
+ #
+ # Around callbacks can access the return value from the event, if it
+ # wasn't halted, from the +yield+ call.
+ #
+ # ===== Options
+ #
+ # * <tt>:if</tt> - A symbol, a string or an array of symbols and strings,
+ # each naming an instance method or a proc; the callback will be called
+ # only when they all return a true value.
+ # * <tt>:unless</tt> - A symbol, a string or an array of symbols and
+ # strings, each naming an instance method or a proc; the callback will
+ # be called only when they all return a false value.
+ # * <tt>:prepend</tt> - If +true+, the callback will be prepended to the
+ # existing chain rather than appended.
+ 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
- end
- # 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: -> { self.age > 18 }
- # end
- #
- # An <tt>ArgumentError</tt> will be raised if the callback has not
- # already been set (unless the <tt>:raise</tt> option is set to <tt>false</tt>).
- 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
+ # 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: -> { self.age > 18 }
+ # end
+ #
+ # An <tt>ArgumentError</tt> will be raised if the callback has not
+ # already been set (unless the <tt>:raise</tt> option is set to <tt>false</tt>).
+ 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
+ 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)
+ chain.delete(callback)
+ end
+ target.set_callbacks name, chain
end
- target.set_callbacks name, chain
end
- end
- # Remove all set callbacks for the given event.
- def reset_callbacks(name)
- callbacks = get_callbacks name
+ # Remove all set callbacks for the given event.
+ def reset_callbacks(name)
+ callbacks = get_callbacks name
- ActiveSupport::DescendantsTracker.descendants(self).each do |target|
- chain = target.get_callbacks(name).dup
- callbacks.each { |c| chain.delete(c) }
- target.set_callbacks name, chain
- end
+ ActiveSupport::DescendantsTracker.descendants(self).each do |target|
+ chain = target.get_callbacks(name).dup
+ callbacks.each { |c| chain.delete(c) }
+ target.set_callbacks name, chain
+ end
- self.set_callbacks name, callbacks.dup.clear
- end
+ self.set_callbacks name, callbacks.dup.clear
+ end
- # Define sets of events in the object life cycle that support callbacks.
- #
- # define_callbacks :validate
- # define_callbacks :initialize, :save, :destroy
- #
- # ===== Options
- #
- # * <tt>:terminator</tt> - Determines when a before filter will halt the
- # callback chain, preventing following before and around callbacks from
- # being called and the event from being triggered.
- # This should be a lambda to be executed.
- # The current object and the result lambda of the callback will be provided
- # to the terminator lambda.
- #
- # define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false }
- #
- # In this example, if any before validate callbacks returns +false+,
- # any successive before and around callback is not executed.
- #
- # The default terminator halts the chain when a callback throws +:abort+.
- #
- # * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
- # callbacks should be terminated by the <tt>:terminator</tt> option. By
- # default after callbacks are executed no matter if callback chain was
- # terminated or not. This option makes sense only when <tt>:terminator</tt>
- # option is specified.
- #
- # * <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
- #
- # def before_save(caller)
- # puts 'Audit: before_save is called'
- # end
- # end
- #
- # class Account
- # include ActiveSupport::Callbacks
- #
- # define_callbacks :save
- # set_callback :save, :before, Audit.new
- #
- # 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
- #
- # 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.
- #
- # A declaration like
- #
- # define_callbacks :save, scope: [:name]
- #
- # would call <tt>Audit#save</tt>.
- #
- # ===== Notes
- #
- # +names+ passed to `define_callbacks` must not end with
- # `!`, `?` or `=`.
- #
- # Calling `define_callbacks` multiple times with the same +names+ will
- # overwrite previous callbacks registered with `set_callback`.
- def define_callbacks(*names)
- options = names.extract_options!
-
- names.each do |name|
- class_attribute "_#{name}_callbacks", instance_writer: false
- set_callbacks name, CallbackChain.new(name, options)
-
- module_eval <<-RUBY, __FILE__, __LINE__ + 1
+ # Define sets of events in the object life cycle that support callbacks.
+ #
+ # define_callbacks :validate
+ # define_callbacks :initialize, :save, :destroy
+ #
+ # ===== Options
+ #
+ # * <tt>:terminator</tt> - Determines when a before filter will halt the
+ # callback chain, preventing following before and around callbacks from
+ # being called and the event from being triggered.
+ # This should be a lambda to be executed.
+ # The current object and the result lambda of the callback will be provided
+ # to the terminator lambda.
+ #
+ # define_callbacks :validate, terminator: ->(target, result_lambda) { result_lambda.call == false }
+ #
+ # In this example, if any before validate callbacks returns +false+,
+ # any successive before and around callback is not executed.
+ #
+ # The default terminator halts the chain when a callback throws +:abort+.
+ #
+ # * <tt>:skip_after_callbacks_if_terminated</tt> - Determines if after
+ # callbacks should be terminated by the <tt>:terminator</tt> option. By
+ # default after callbacks are executed no matter if callback chain was
+ # terminated or not. This option makes sense only when <tt>:terminator</tt>
+ # option is specified.
+ #
+ # * <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
+ #
+ # def before_save(caller)
+ # puts 'Audit: before_save is called'
+ # end
+ # end
+ #
+ # class Account
+ # include ActiveSupport::Callbacks
+ #
+ # define_callbacks :save
+ # set_callback :save, :before, Audit.new
+ #
+ # 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
+ #
+ # 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.
+ #
+ # A declaration like
+ #
+ # define_callbacks :save, scope: [:name]
+ #
+ # would call <tt>Audit#save</tt>.
+ #
+ # ===== Notes
+ #
+ # +names+ passed to `define_callbacks` must not end with
+ # `!`, `?` or `=`.
+ #
+ # Calling `define_callbacks` multiple times with the same +names+ will
+ # overwrite previous callbacks registered with `set_callback`.
+ def define_callbacks(*names)
+ options = names.extract_options!
+
+ names.each do |name|
+ class_attribute "_#{name}_callbacks", instance_writer: false
+ set_callbacks name, CallbackChain.new(name, options)
+
+ module_eval <<-RUBY, __FILE__, __LINE__ + 1
def _run_#{name}_callbacks(&block)
__run_callbacks__(_#{name}_callbacks, &block)
end
RUBY
+ end
end
- end
- protected
+ protected
- def get_callbacks(name) # :nodoc:
- send "_#{name}_callbacks"
- end
+ def get_callbacks(name) # :nodoc:
+ send "_#{name}_callbacks"
+ end
- def set_callbacks(name, callbacks) # :nodoc:
- send "_#{name}_callbacks=", callbacks
- end
+ def set_callbacks(name, callbacks) # :nodoc:
+ send "_#{name}_callbacks=", callbacks
+ end
- def deprecated_false_terminator # :nodoc:
- Proc.new do |target, result_lambda|
- terminate = true
- catch(:abort) do
- result = result_lambda.call if result_lambda.is_a?(Proc)
- if Callbacks.halt_and_display_warning_on_return_false && result == false
- display_deprecation_warning_for_false_terminator
- else
- terminate = false
+ def deprecated_false_terminator # :nodoc:
+ Proc.new do |target, result_lambda|
+ terminate = true
+ catch(:abort) do
+ result = result_lambda.call if result_lambda.is_a?(Proc)
+ if Callbacks.halt_and_display_warning_on_return_false && result == false
+ display_deprecation_warning_for_false_terminator
+ else
+ terminate = false
+ end
+ end
+ terminate
end
end
- terminate
- end
- end
- private
+ private
- def display_deprecation_warning_for_false_terminator
- ActiveSupport::Deprecation.warn(<<-MSG.squish)
+ def display_deprecation_warning_for_false_terminator
+ ActiveSupport::Deprecation.warn(<<-MSG.squish)
Returning `false` in Active Record and Active Model callbacks will not implicitly halt a callback chain in Rails 5.1.
To explicitly halt the callback chain, please use `throw :abort` instead.
MSG
+ end
end
- end
end
end