Module: CouchRest::Callbacks::ClassMethods

Defined in:
lib/couchrest/mixins/callbacks.rb

Constant Summary collapse

CHAINS =
{:before => :before, :around => :before, :after => :after}

Instance Method Summary collapse

Instance Method Details

#_create_and_run_keyed_callback(klass, kind, key, obj, &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.



378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
# File 'lib/couchrest/mixins/callbacks.rb', line 378

def _create_and_run_keyed_callback(klass, kind, key, obj, &blk)
  @_keyed_callbacks ||= {}
  @_keyed_callbacks[[kind, key]] ||= begin
    str = self.send("_#{kind}_callbacks").compile(key, :object => obj, :terminator => self.send("_#{kind}_terminator"))

    self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
      def _run__#{klass.split("::").last}__#{kind}__#{key}__callbacks
        #{str}
      end
    RUBY_EVAL
              
    true
  end
                            
  obj.send("_run__#{klass.split("::").last}__#{kind}__#{key}__callbacks", &blk)
end

#_define_runner(symbol, str, options) ⇒ Object

Make the _run_save_callbacks 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_save_callbacks do

save

end

The _run_save_callbacks 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.



358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
# File 'lib/couchrest/mixins/callbacks.rb', line 358

def _define_runner(symbol, str, options)        
  str = <<-RUBY_EVAL
    def _run_#{symbol}_callbacks(key = nil)
      if key
        send("_run__\#{self.class.name.split("::").last}__#{symbol}__\#{key}__callbacks") { yield if block_given? }
      else
        #{str}
      end
    end
  RUBY_EVAL
  
  class_eval str, __FILE__, __LINE__ + 1
  
  before_name, around_name, after_name = 
    options.values_at(:before, :after, :around)
end

#define_callbacks(*symbols) ⇒ Object

Define callbacks.

Creates a <name>_callback method that you can use to add callbacks.

Syntax:

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

The <name>_callback method also updates the run<name>_callbacks method, which is the public API to run the callbacks.

Also creates a skip_<name>_callback method that you can use to skip callbacks.

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_dispatch_callbacks(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.



426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
# File 'lib/couchrest/mixins/callbacks.rb', line 426

def define_callbacks(*symbols)
  terminator = symbols.pop if symbols.last.is_a?(String)
  symbols.each do |symbol|
    self.extlib_inheritable_accessor("_#{symbol}_terminator")
    self.send("_#{symbol}_terminator=", terminator)
    self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
      extlib_inheritable_accessor :_#{symbol}_callbacks
      self._#{symbol}_callbacks = CallbackChain.new(:#{symbol})

      def self.#{symbol}_callback(*filters, &blk)
        type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
        options = filters.last.is_a?(Hash) ? filters.pop : {}
        filters.unshift(blk) if block_given?
        
        filters.map! do |filter| 
          # overrides parent class
          self._#{symbol}_callbacks.delete_if {|c| c.matches?(type, :#{symbol}, filter)}
          Callback.new(filter, type, options.dup, self, :#{symbol})
        end
        self._#{symbol}_callbacks.push(*filters)
        _define_runner(:#{symbol}, 
          self._#{symbol}_callbacks.compile(nil, :terminator => _#{symbol}_terminator), 
          options)
      end
      
      def self.skip_#{symbol}_callback(*filters, &blk)
        type = [:before, :after, :around].include?(filters.first) ? filters.shift : :before
        options = filters.last.is_a?(Hash) ? filters.pop : {}
        filters.unshift(blk) if block_given?
        filters.each do |filter|
          self._#{symbol}_callbacks = self._#{symbol}_callbacks.clone(self)
          
          filter = self._#{symbol}_callbacks.find {|c| c.matches?(type, :#{symbol}, filter) }
          per_key = options[:per_key] || {}
          if filter
            filter.recompile!(options, per_key)
          else
            self._#{symbol}_callbacks.delete(filter)
          end
          _define_runner(:#{symbol}, 
            self._#{symbol}_callbacks.compile(nil, :terminator => _#{symbol}_terminator), 
            options)
        end
        
      end
      
      def self.reset_#{symbol}_callbacks
        self._#{symbol}_callbacks = CallbackChain.new(:#{symbol})
        _define_runner(:#{symbol}, self._#{symbol}_callbacks.compile, {})
      end
      
      self.#{symbol}_callback(:before)
    RUBY_EVAL
  end
end