论坛首页 编程语言技术论坛

Rails源码研究之ActiveRecord:五,Callbacks

浏览 3441 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2007-06-22  
Callbacks相关的源码在callbacks.rb文件里:
module ActiveRecord
  module Callbacks
    CALLBACKS = %w(
      after_find after_initialize before_save after_save before_create after_create before_update after_update before_validation
      after_validation before_validation_on_create after_validation_on_create before_validation_on_update
      after_validation_on_update before_destroy after_destroy
    )

    def self.included(base) #:nodoc:
      base.extend(ClassMethods)
      base.class_eval do
        class << self
          include Observable
          alias_method_chain :instantiate, :callbacks
        end

        [:initialize, :create_or_update, :valid?, :create, :update, :destroy].each do |method|
          alias_method_chain method, :callbacks
        end
      end

      CALLBACKS.each do |method|
        base.class_eval <<-"end_eval"
          def self.#{method}(*callbacks, &block)
            callbacks << block if block_given?
            write_inheritable_array(#{method.to_sym.inspect}, callbacks)
          end
        end_eval
      end
    end

    module ClassMethods #:nodoc:
      def instantiate_with_callbacks(record)
        object = instantiate_without_callbacks(record)

        if object.respond_to_without_attributes?(:after_find)
          object.send(:callback, :after_find)
        end

        if object.respond_to_without_attributes?(:after_initialize)
          object.send(:callback, :after_initialize)
        end

        object
      end
    end

    def create_or_update_with_callbacks #:nodoc:
      return false if callback(:before_save) == false
      result = create_or_update_without_callbacks
      callback(:after_save)
      result
    end

    def create_with_callbacks #:nodoc:
      return false if callback(:before_create) == false
      result = create_without_callbacks
      callback(:after_create)
      result
    end

    def update_with_callbacks #:nodoc:
      return false if callback(:before_update) == false
      result = update_without_callbacks
      callback(:after_update)
      result
    end

    def valid_with_callbacks? #:nodoc:
      return false if callback(:before_validation) == false
      if new_record? then result = callback(:before_validation_on_create) else result = callback(:before_validation_on_update) end
      return false if result == false

      result = valid_without_callbacks?

      callback(:after_validation)
      if new_record? then callback(:after_validation_on_create) else callback(:after_validation_on_update) end

      return result
    end

    def destroy_with_callbacks #:nodoc:
      return false if callback(:before_destroy) == false
      result = destroy_without_callbacks
      callback(:after_destroy)
      result
    end

    private
      def callback(method)
        notify(method)

        callbacks_for(method).each do |callback|
          result = case callback
            when Symbol
              self.send(callback)
            when String
              eval(callback, binding)
            when Proc, Method
              callback.call(self)
            else
              if callback.respond_to?(method)
                callback.send(method, self)
              else
                raise ActiveRecordError, "Callbacks must be a symbol denoting the method to call, a string to be evaluated, a block to be invoked, or an object responding to the callback method."
              end
          end
          return false if result == false
        end

        result = send(method) if respond_to_without_attributes?(method)

        return result
      end

      def callbacks_for(method)
        self.class.read_inheritable_attribute(method.to_sym) or []
      end

  end
end


从源码中我们可以学习如下几点:
1,有四种类型的callback macros:
1)Method references(symbol)
class Topic < ActiveRecord::Base
  before_destroy :destroy_author
end

2)Callback objects
class BankAccount < ActiveRecord::Base
  before_save      EncryptionWrapper.new("credit_card_number")
  after_save       EncryptionWrapper.new("credit_card_number")
  after_initialize EncryptionWrapper.new("credit_card_number")
end

class EncryptionWrapper
  def initialize(attribute)
    @attribute = attribute
  end

  def before_save(record)
    record.credit_card_number = encrypt(record.credit_card_number)
  end

  def after_save(record)
    record.credit_card_number = decrypt(record.credit_card_number)
  end

  alias_method :after_find, :after_save

  private
    def encrypt(value)
      # Secrecy is committed
    end

    def decrypt(value)
      # Secrecy is unveiled
    end
end

3)Inline methods(proc)
class Firm < ActiveRecord::Base
  # Destroys the associated clients and people when the firm is destroyed
  before_destroy { |record| Person.destroy_all "firm_id = #{record.id}"   }
  before_destroy { |record| Client.destroy_all "client_of = #{record.id}" }
end

4)Inline eval methods(string)
class Topic < ActiveRecord::Base
  before_destroy 'self.class.delete_all "parent_id = #{id}"'
end


2,执行Model的如下方法时会调用callbacks:
instantiate(find)、initialize、create_or_update、valid?、create、update、destroy

3,activesupport\lib\activesupport\core_text\module\aliasing.rb:
class Module
  def alias_method_chain(target, feature)
    aliased_target, punctuation = target.to_s.sub(/([?!=])$/, ''), $1
    yield(aliased_target, punctuation) if block_given?
    alias_method "#{aliased_target}_without_#{feature}#{punctuation}", target
    alias_method target, "#{aliased_target}_with_#{feature}#{punctuation}"
  end
end

比如alias_method_chain :destroy, :callbacks的效果是调用destroy方法时会调用destroy_with_callbacks
而destroy_with_callbacks方法先调用callback(:before_destroy),然后destroy_without_callbacks(又被alias为destroy),然后callback(:after_destroy)
然后callback(:before_destroy)和callback(:after_destroy)分别调用before_destroy和after_destroy后面的macros
ActiveRecord的Callback就像一个Around_filter,实现了aop拦截的功能
论坛首页 编程语言技术版

跳转论坛:
Global site tag (gtag.js) - Google Analytics