aboutsummaryrefslogblamecommitdiffstats
path: root/activemodel/lib/active_model/callbacks.rb
blob: a7e0cf90c1baf9670cf58960a04ce0564a25ff8d (plain) (tree)
1
2
3


                                  













































                                                                                           






                                        


                                                                                        
     















                                                                                         
                     
                                       
                                        

                                    
           



                                                                              

             
          











































                                                                                             
require 'active_support/callbacks'

module ActiveModel
  # == Active Model Callbacks
  # 
  # Provides an interface for any class to have Active Record like callbacks.
  # 
  # Like the Active Record methods, the call back 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 call backs attached to:
  # 
  #   define_model_callbacks :create, :update
  # 
  # This will provide all three standard callbacks (before, around and after) around
  # both the :create and :update methods. To implement, you need to wrap the methods 
  # you want call backs on in a block so that the call backs get a chance to fire:
  # 
  #   def create
  #     _run_create_callbacks do
  #       # Your create action methods here
  #     end
  #   end
  # 
  # The _run_<method_name>_callbacks methods are dynamically created when you extend
  # the <tt>ActiveModel::Callbacks</tt> module.
  # 
  # 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
  # 
  # You can choose not to have all three callbacks by passing an 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)
      base.class_eval do
        include ActiveSupport::Callbacks
      end
    end

    # define_model_callbacks accepts all options define_callbacks does, in case you
    # want to overwrite a default. Besides that, it also accepts an :only 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 call back 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", :scope => [:kind, :name] }.merge(options)

      types = Array(options.delete(:only))
      types = [:before, :around, :after] if types.empty?

      callbacks.each do |callback|
        define_callbacks(callback, options)

        types.each do |type|
          send(:"_define_#{type}_model_callback", self, callback)
        end
      end
    end

    def _define_before_model_callback(klass, callback) #:nodoc:
      klass.class_eval <<-CALLBACK, __FILE__, __LINE__
        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__
        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__
        def self.after_#{callback}(*args, &block)
          options = args.extract_options!
          options[:prepend] = true
          options[:if] = Array(options[:if]) << "!halted && value != false"
          set_callback(:#{callback}, :after, *(args << options), &block)
        end
      CALLBACK
    end
  end
end