method triggers: instead, before, after
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