Never been to DZone Snippets before?

Snippets is a public source code repository. Easily build up your personal collection of code snippets, categorize them with tags / keywords, and share them with the world

About this user

« Newer Snippets
Older Snippets »
Showing 1-1 of 1 total  RSS 

method triggers: instead, before, after

Redefine method calls, in manner similar to SQL triggers and PostgreSQL rules - i.e. execute code block instead, before or after call to original method. rules can be appended and removed.

class Object

  def self.__rules__
  # container for defined rules, each item is:
  #   [class, event_name, method_name, alias_for_original_method, caller, comment]
    @@rules ||= []
  end

  def self.__create_rule_instead( method, comment = '', &block) # creates and returns new rule
    b_id = "%04x" % block.object_id
    old_method_name = :"__previous_#{method}_#{b_id}"
    alias_method old_method_name, method
    define_method method, &block
    __rules__ << rule = [self, "INSTEAD", method, old_method_name,caller[0], comment ]
    rule
  end

  def self.__create_rule_before( method, comment = '', &block)
    args = instance_method(method).arity == 0 ? '' : '(*args)'
    b_id = "%04x" % block.object_id
    old_method_name = :"__previous_#{method}_#{b_id}"
    alias_method old_method_name, method
    define_method :"__before_#{method}_#{b_id}", &block
    class_eval <<-EOT
      def #{method}#{args}
        __before_#{method}_#{b_id}#{args}
        __previous_#{method}_#{b_id}#{args}
      end
    EOT
    __rules__ << rule = [self, "BEFORE", method, old_method_name, caller[0], comment]
    rule
  end

  def self.__create_rule_after( method, comment = '', &block)
    args = instance_method(method).arity == 0 ? '' : '(*args)'
    b_id = "%04x" % block.object_id
    old_method_name = :"__previous_#{method}_#{b_id}"
    alias_method old_method_name, method
    define_method :"__after_#{method}_#{b_id}", &block
    class_eval <<-EOT
      def #{method}#{args}
        res = __previous_#{method}_#{b_id}#{args}
        __after_#{method}_#{b_id}#{args}
        res
      end
    EOT
    __rules__ << rule = [self, "AFTER", method, old_method_name,caller[0], comment ]
    rule
  end

  def self.__remove_rule( rule ) # has some bugs when rules on subclasses are defined :(
    idx = __rules__.index(rule)
    if idx
      # look for next rule for the same method
      idx += 1
      while idx < __rules__.size
        break if  __rules__[idx][2] == rule[2] && __rules__[idx][0] == rule[0]
        idx+=1
      end
      if idx < __rules__.size
        next_rule = __rules__[idx]
        next_rule[0].send :remove_method, next_rule[3]
        next_rule[0].send :alias_method, next_rule[3], rule[3]
      else
        # that was last
        rule[0].send :remove_method, rule[2]
        rule[0].send :alias_method, rule[2], rule[3]
      end
      __rules__.delete(rule)
    end
  end

end


Example:
class Model
  def save
    puts "save"
  end
  def reload(flag)
    "reloaded"
  end
end

r1 = Model.__create_rule_instead(:reload) {|flag| flag ? "FRESH" : "STALE" }

obj = Model.new

puts "RELOAD:"+obj.reload(true)
puts "RELOAD:"+obj.reload(false)

Object.__remove_rule(r1)
puts "RELOAD:"+obj.reload(false)

Model.__create_rule_before(:save) { puts "BEFORE SAVE" }
r2 = Model.__create_rule_before(:save) { puts "YET BEFORE SAVE" }
Model.__create_rule_after(:save)  { puts "AFTER SAVE" }
r3 = Model.__create_rule_after(:save)  { puts "YET AFTER SAVE" }
obj.save
Object.__remove_rule(r2)
puts "----------"
obj.save
Object.__remove_rule(r3)
puts "----------"
obj.save

produces:
RELOAD:FRESH
RELOAD:STALE
RELOAD:reloaded
YET BEFORE SAVE
BEFORE SAVE
save
AFTER SAVE
YET AFTER SAVE
----------
BEFORE SAVE
save
AFTER SAVE
YET AFTER SAVE
----------
BEFORE SAVE
save
AFTER SAVE
« Newer Snippets
Older Snippets »
Showing 1-1 of 1 total  RSS