Module: ActiveSupport::Callbacks

Overview

Callbacks are code hooks that are run at key points in an object’s life cycle. 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 the events in the object’s life cycle 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).

By default callbacks are halted by throwing :abort. See ClassMethods#define_callbacks for details.

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.

class Record
  include ActiveSupport::Callbacks
  define_callbacks :save

  def save
    run_callbacks :save do
      puts "- save"
    end
  end
end

class PersonRecord < Record
  set_callback :save, :before, :saving_message
  def saving_message
    puts "saving..."
  end

  set_callback :save, :after do |object|
    puts "saved"
  end
end

person = PersonRecord.new
person.save

Output:

saving...
- save
saved

Defined Under Namespace

Modules: CallTemplate, ClassMethods, Conditionals, Filters Classes: Callback, CallbackChain, CallbackSequence

Constant Summary collapse

CALLBACK_FILTER_TYPES =
[:before, :after, :around].freeze

Instance Method Summary collapse

Methods included from Concern

append_features, class_methods, extended, included, prepend_features, prepended

Instance Method Details

#run_callbacks(kind, type = nil) ⇒ Object

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.

If the callback chain was halted, returns false. Otherwise returns the result of the block, nil if no callbacks have been set, or true if callbacks have been set but no block is given.

run_callbacks :save do
  save
end

As this method is used in many places, and often wraps large portions of user code, it has an additional design goal of minimizing its impact on the visible call stack. An exception from inside a :before or :after callback can be as noisy as it likes – but when control has passed smoothly through and into the supplied block, we want as little evidence as possible that we were here.



95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'activesupport/lib/active_support/callbacks.rb', line 95

def run_callbacks(kind, type = nil)
  callbacks = __callbacks[kind.to_sym]

  if callbacks.empty?
    yield if block_given?
  else
    env = Filters::Environment.new(self, false, nil)

    next_sequence = callbacks.compile(type)

    # Common case: no 'around' callbacks defined
    if next_sequence.final?
      next_sequence.invoke_before(env)
      env.value = !env.halted && (!block_given? || yield)
      next_sequence.invoke_after(env)
      env.value
    else
      invoke_sequence = Proc.new do
        skipped = nil

        while true
          current = next_sequence
          current.invoke_before(env)
          if current.final?
            env.value = !env.halted && (!block_given? || yield)
          elsif current.skip?(env)
            (skipped ||= []) << current
            next_sequence = next_sequence.nested
            next
          else
            next_sequence = next_sequence.nested
            begin
              target, block, method, *arguments = current.expand_call_template(env, invoke_sequence)
              target.send(method, *arguments, &block)
            ensure
              next_sequence = current
            end
          end
          current.invoke_after(env)
          skipped.pop.invoke_after(env) while skipped&.first
          break env.value
        end
      end

      invoke_sequence.call
    end
  end
end