<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DZone Snippets: metaprogramming code</title>
    <link>http://snippets.dzone.com/posts</link>
    <pubDate>Thu, 24 Jul 2008 23:14:40 GMT</pubDate>
    <description>DZone Snippets: metaprogramming code</description>
    <item>
      <title>method triggers: instead, before, after</title>
      <link>http://snippets.dzone.com/posts/show/3620</link>
      <description>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.&lt;br /&gt;&lt;br /&gt;&lt;code&gt;&lt;br /&gt;class Object&lt;br /&gt;&lt;br /&gt;  def self.__rules__&lt;br /&gt;  # container for defined rules, each item is:&lt;br /&gt;  #   [class, event_name, method_name, alias_for_original_method, caller, comment]&lt;br /&gt;    @@rules ||= []&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def self.__create_rule_instead( method, comment = '', &amp;block) # creates and returns new rule&lt;br /&gt;    b_id = "%04x" % block.object_id&lt;br /&gt;    old_method_name = :"__previous_#{method}_#{b_id}"&lt;br /&gt;    alias_method old_method_name, method&lt;br /&gt;    define_method method, &amp;block&lt;br /&gt;    __rules__ &lt;&lt; rule = [self, "INSTEAD", method, old_method_name,caller[0], comment ]&lt;br /&gt;    rule&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def self.__create_rule_before( method, comment = '', &amp;block)&lt;br /&gt;    args = instance_method(method).arity == 0 ? '' : '(*args)'&lt;br /&gt;    b_id = "%04x" % block.object_id&lt;br /&gt;    old_method_name = :"__previous_#{method}_#{b_id}"&lt;br /&gt;    alias_method old_method_name, method&lt;br /&gt;    define_method :"__before_#{method}_#{b_id}", &amp;block&lt;br /&gt;    class_eval &lt;&lt;-EOT&lt;br /&gt;      def #{method}#{args}&lt;br /&gt;        __before_#{method}_#{b_id}#{args}&lt;br /&gt;        __previous_#{method}_#{b_id}#{args}&lt;br /&gt;      end&lt;br /&gt;    EOT&lt;br /&gt;    __rules__ &lt;&lt; rule = [self, "BEFORE", method, old_method_name, caller[0], comment]&lt;br /&gt;    rule&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def self.__create_rule_after( method, comment = '', &amp;block)&lt;br /&gt;    args = instance_method(method).arity == 0 ? '' : '(*args)'&lt;br /&gt;    b_id = "%04x" % block.object_id&lt;br /&gt;    old_method_name = :"__previous_#{method}_#{b_id}"&lt;br /&gt;    alias_method old_method_name, method&lt;br /&gt;    define_method :"__after_#{method}_#{b_id}", &amp;block&lt;br /&gt;    class_eval &lt;&lt;-EOT&lt;br /&gt;      def #{method}#{args}&lt;br /&gt;        res = __previous_#{method}_#{b_id}#{args}&lt;br /&gt;        __after_#{method}_#{b_id}#{args}&lt;br /&gt;        res&lt;br /&gt;      end&lt;br /&gt;    EOT&lt;br /&gt;    __rules__ &lt;&lt; rule = [self, "AFTER", method, old_method_name,caller[0], comment ]&lt;br /&gt;    rule&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;  def self.__remove_rule( rule ) # has some bugs when rules on subclasses are defined :(&lt;br /&gt;    idx = __rules__.index(rule)&lt;br /&gt;    if idx&lt;br /&gt;      # look for next rule for the same method&lt;br /&gt;      idx += 1&lt;br /&gt;      while idx &lt; __rules__.size&lt;br /&gt;        break if  __rules__[idx][2] == rule[2] &amp;&amp; __rules__[idx][0] == rule[0]&lt;br /&gt;        idx+=1&lt;br /&gt;      end&lt;br /&gt;      if idx &lt; __rules__.size&lt;br /&gt;        next_rule = __rules__[idx]&lt;br /&gt;        next_rule[0].send :remove_method, next_rule[3]&lt;br /&gt;        next_rule[0].send :alias_method, next_rule[3], rule[3]&lt;br /&gt;      else&lt;br /&gt;        # that was last&lt;br /&gt;        rule[0].send :remove_method, rule[2]&lt;br /&gt;        rule[0].send :alias_method, rule[2], rule[3]&lt;br /&gt;      end&lt;br /&gt;      __rules__.delete(rule)&lt;br /&gt;    end&lt;br /&gt;  end&lt;br /&gt;&lt;br /&gt;end&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;&lt;br /&gt;Example:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;class Model&lt;br /&gt;  def save&lt;br /&gt;    puts "save"&lt;br /&gt;  end&lt;br /&gt;  def reload(flag)&lt;br /&gt;    "reloaded"&lt;br /&gt;  end&lt;br /&gt;end&lt;br /&gt;&lt;br /&gt;r1 = Model.__create_rule_instead(:reload) {|flag| flag ? "FRESH" : "STALE" }&lt;br /&gt;&lt;br /&gt;obj = Model.new&lt;br /&gt;&lt;br /&gt;puts "RELOAD:"+obj.reload(true)&lt;br /&gt;puts "RELOAD:"+obj.reload(false)&lt;br /&gt;&lt;br /&gt;Object.__remove_rule(r1)&lt;br /&gt;puts "RELOAD:"+obj.reload(false)&lt;br /&gt;&lt;br /&gt;Model.__create_rule_before(:save) { puts "BEFORE SAVE" }&lt;br /&gt;r2 = Model.__create_rule_before(:save) { puts "YET BEFORE SAVE" }&lt;br /&gt;Model.__create_rule_after(:save)  { puts "AFTER SAVE" }&lt;br /&gt;r3 = Model.__create_rule_after(:save)  { puts "YET AFTER SAVE" }&lt;br /&gt;obj.save&lt;br /&gt;Object.__remove_rule(r2)&lt;br /&gt;puts "----------"&lt;br /&gt;obj.save&lt;br /&gt;Object.__remove_rule(r3)&lt;br /&gt;puts "----------"&lt;br /&gt;obj.save&lt;br /&gt;&lt;/code&gt;&lt;br /&gt;produces:&lt;br /&gt;&lt;code&gt;&lt;br /&gt;RELOAD:FRESH&lt;br /&gt;RELOAD:STALE&lt;br /&gt;RELOAD:reloaded&lt;br /&gt;YET BEFORE SAVE&lt;br /&gt;BEFORE SAVE&lt;br /&gt;save&lt;br /&gt;AFTER SAVE&lt;br /&gt;YET AFTER SAVE&lt;br /&gt;----------&lt;br /&gt;BEFORE SAVE&lt;br /&gt;save&lt;br /&gt;AFTER SAVE&lt;br /&gt;YET AFTER SAVE&lt;br /&gt;----------&lt;br /&gt;BEFORE SAVE&lt;br /&gt;save&lt;br /&gt;AFTER SAVE&lt;br /&gt;&lt;/code&gt;</description>
      <pubDate>Sat, 03 Mar 2007 10:50:20 GMT</pubDate>
      <guid>http://snippets.dzone.com/posts/show/3620</guid>
      <author>dseverin ()</author>
    </item>
  </channel>
</rss>
