Module: ActiveSupport::Callbacks::ClassMethods

Defined in:
lib/active_support/callbacks.rb

Instance Method Summary collapse

Instance Method Details

#__create_keyed_callback(name, kind, object, &blk) ⇒ Object

This is called the first time a callback is called with a particular key. It creates a new callback method for the key, calculating which callbacks can be omitted because of per_key conditions.



422
423
424
425
426
427
428
429
# File 'lib/active_support/callbacks.rb', line 422

def __create_keyed_callback(name, kind, object, &blk) #:nodoc:
  @_keyed_callbacks ||= {}
  @_keyed_callbacks[name] ||= begin
    str = send("_#{kind}_callbacks").compile(name, object)
    class_eval "def #{name}() #{str} end", __FILE__, __LINE__
    true
  end
end

#__define_runner(symbol) ⇒ Object

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.



385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
# File 'lib/active_support/callbacks.rb', line 385

def __define_runner(symbol) #:nodoc:
  send("_update_#{symbol}_superclass_callbacks")
  body = send("_#{symbol}_callbacks").compile(nil)

  silence_warnings do
    undef_method "_run_#{symbol}_callbacks" if method_defined?("_run_#{symbol}_callbacks")
    class_eval "      def _run_\#{symbol}_callbacks(key = nil, &blk)\n        @_initialized_\#{symbol}_callbacks ||= begin\n          if self.class.send(\"_update_\#{symbol}_superclass_callbacks\")\n            self.class.__define_runner(\#{symbol.inspect})\n            return _run_\#{symbol}_callbacks(key, &blk)\n          end\n          true\n        end\n\n        if key\n          name = \"_run__\\\#{self.class.name.hash.abs}__\#{symbol}__\\\#{key.hash.abs}__callbacks\"\n\n          unless respond_to?(name)\n            self.class.__create_keyed_callback(name, :\#{symbol}, self, &blk)\n          end\n\n          send(name, &blk)\n        else\n          \#{body}\n        end\n      end\n      private :_run_\#{symbol}_callbacks\n    RUBY_EVAL\n  end\nend\n", __FILE__, __LINE__ + 1

#__update_callbacks(name, filters = [], block = nil) {|chain, type, filters, options| ... } ⇒ Object

This is used internally to append, prepend and skip callbacks to the CallbackChain.

Yields:

  • (chain, type, filters, options)


434
435
436
437
438
439
440
441
442
443
444
445
# File 'lib/active_support/callbacks.rb', line 434

def __update_callbacks(name, filters = [], block = nil) #:nodoc:
  send("_update_#{name}_superclass_callbacks")

  type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
  options = filters.last.is_a?(Hash) ? filters.pop : {}
  filters.unshift(block) if block

  chain = send("_#{name}_callbacks")
  yield chain, type, filters, options if block_given?

  __define_runner(name)
end

#define_callbacks(*callbacks) ⇒ Object

Define callbacks types.

Example

define_callbacks :validate

Options

  • :terminator - Indicates when a before filter is considered

to be halted.

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”.

  • :rescuable - By default, after filters are not executed if

the given block or an before_filter raises an error. Supply :rescuable => true to change this behavior.

  • :scope - Show which methods should be executed when a class

is given as callback:

define_callbacks :filters, :scope => [ :kind ]

When a class is given:

before_filter MyFilter

It will call the type of the filter in the given class, which in this case, is “before”.

If, for instance, you supply the given scope:

define_callbacks :validate, :scope => [ :kind, :name ]

It will call “#kind_#name” in the given class. So “before_validate” will be called in the class below:

before_validate MyValidation

Defaults to :kind.



556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
# File 'lib/active_support/callbacks.rb', line 556

def define_callbacks(*callbacks)
  config = callbacks.last.is_a?(Hash) ? callbacks.pop : {}
  callbacks.each do |callback|
    extlib_inheritable_reader("_#{callback}_callbacks") do
      CallbackChain.new(callback, config)
    end

    extlib_inheritable_reader("_removed_#{callback}_callbacks") do
      []
    end

    class_eval "      def self._\#{callback}_superclass_callbacks\n        if superclass.respond_to?(:_\#{callback}_callbacks)\n          superclass._\#{callback}_callbacks + superclass._\#{callback}_superclass_callbacks\n        else\n          []\n        end\n      end\n\n      def self._update_\#{callback}_superclass_callbacks\n        changed, index = false, 0\n\n        callbacks  = (_\#{callback}_superclass_callbacks -\n          _\#{callback}_callbacks) - _removed_\#{callback}_callbacks\n\n        callbacks.each do |callback|\n          if new_index = _\#{callback}_callbacks.index(callback)\n            index = new_index + 1\n          else\n            changed = true\n            _\#{callback}_callbacks.insert(index, callback)\n            index = index + 1\n          end\n        end\n        changed\n      end\n    METHOD\n\n    __define_runner(callback)\n  end\nend\n", __FILE__, __LINE__ + 1

#reset_callbacks(symbol) ⇒ Object

Reset callbacks for a given type.



506
507
508
509
510
511
# File 'lib/active_support/callbacks.rb', line 506

def reset_callbacks(symbol)
  callbacks = send("_#{symbol}_callbacks")
  callbacks.clear
  send("_removed_#{symbol}_callbacks").concat(callbacks)
  __define_runner(symbol)
end

#set_callback(name, *filter_list, &block) ⇒ Object

Set callbacks for a previously defined callback.

Syntax:

set_callback :save, :before, :before_meth
set_callback :save, :after,  :after_meth, :if => :condition
set_callback :save, :around, lambda { |r| stuff; yield; stuff }

Use skip_callback to skip any defined one.

When creating or skipping callbacks, you can specify conditions that are always the same for a given key. For instance, in ActionPack, we convert :only and :except conditions into per-key conditions.

before_filter :authenticate, :except => "index"

becomes

dispatch_callback :before, :authenticate, :per_key => {:unless => proc {|c| c.action_name == "index"}}

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(:dispatch, action_name) { ... dispatch stuff ... }

In that case, each action_name would get its own compiled callback method that took into consideration the per_key conditions. This is a speed improvement for ActionPack.



473
474
475
476
477
478
479
480
481
482
483
# File 'lib/active_support/callbacks.rb', line 473

def set_callback(name, *filter_list, &block)
  __update_callbacks(name, filter_list, block) do |chain, type, filters, options|
    filters.map! do |filter|
      removed = chain.delete_if {|c| c.matches?(type, filter) } 
      send("_removed_#{name}_callbacks").push(*removed)
      Callback.new(chain, filter, type, options.dup, self)
    end

    options[:prepend] ? chain.unshift(*filters) : chain.push(*filters)
  end
end

#skip_callback(name, *filter_list, &block) ⇒ Object

Skip a previously defined callback for a given type.



487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/active_support/callbacks.rb', line 487

def skip_callback(name, *filter_list, &block)
  __update_callbacks(name, filter_list, block) do |chain, type, filters, options|
    filters.each do |filter|
      filter = chain.find {|c| c.matches?(type, filter) }

      if filter && options.any?
        new_filter = filter.clone(chain, self)
        chain.insert(chain.index(filter), new_filter)
        new_filter.recompile!(options, options[:per_key] || {})
      end

      chain.delete(filter)
      send("_removed_#{name}_callbacks") << filter
    end
  end
end