class ActiveRecordWithoutCallbacks

This class can disable all callbacks for a model, run a block and then re-enable all callbacks.

Constants

TRUE_BLOCK

Public Class Methods

wo_callbacks(args) { || ... } click to toggle source
# File lib/active-record-without-callbacks.rb, line 5
def self.wo_callbacks(args)
  klass = args[:class]
  @debug = args[:debug]
  
  #Callback-types that we will clear or recompile.
  callbacks = [:create, :find, :initialize, :destroy, :commit, :rollback, :save, :touch, :update, :validate, :validation]
  callback_types = [:before, :after]
  previous_values = []
  
  #List of various possible callbacks.
  takes = [:_create_callbacks, :_find_callbacks, :_initialize_callbacks, :_destroy_callbacks, :_commit_callbacks, :_rollback_callbacks, :_save_callbacks, :_touch_callbacks, :_update_callbacks, :_validate_callbacks, :_validation_callbacks]
  
  #Run inside exclusive, so we are sure no other thread suffers from the horrible hack, that we are about to do.
  Thread.exclusive do
    callbacks.each do |callback|
      puts "Disabling callbacks for '#{callback}'." if @debug
      
      #Method-name for callbacks to reset.
      callbacks_method_name = "_#{callback}_callbacks"
      
      #Store previous value for restoring later.
      previous_values << {
        :callbacks_method_name => callbacks_method_name,
        :value => klass.__send__(callbacks_method_name).clone
      }
      
      #Clear all callbacks.
      puts "Clearing callbacks in '#{callbacks_method_name}'." if @debug
      klass.__send__(callbacks_method_name).clear
      
      #Test that callbacks really was cleared.
      puts "Checking if everything is okay for '#{callback}'." if @debug
      raise "Expected '#{callbacks_method_name}' to be empty but it wasnt: #{klass.__send__(callbacks_method_name)}" unless klass.__send__(callbacks_method_name).empty?
      
      #Trick ActiveRecord to recompile callbacks.
      callback_types.each do |callback_type|
        method_name = "#{callback_type}_#{callback}"
        
        if klass.respond_to?(method_name)
          klass.__send__(method_name, &TRUE_BLOCK)
        end
      end
    end
    
    begin
      #Run the given block, now that all callbacks are disabled.
      puts "Yielding block." if @debug
      
      klass.observers.disable :all do
        return yield
      end
    ensure
      puts "Restoring callbacks." if @debug
      
      #Restore previous callbacks.
      previous_values.each do |previous_value|
        method_name = "#{previous_value[:callbacks_method_name]}="
        klass.__send__(method_name, previous_value[:value])
      end
      
      #Trick ActiveRecord to recompile old callbacks.
      callbacks.each do |callback|
        callback_types.each do |callback_type|
          method_name = "#{callback_type}_#{callback}"
          
          if klass.respond_to?(method_name)
            klass.__send__(method_name, &TRUE_BLOCK)
          end
        end
      end
    end
  end
end