On 4/14/06, Ruby Quiz <james / grayproductions.net> wrote:> The three rules of Ruby Quiz:>> 1.  Please do not post any solutions or spoiler discussion for this quiz until> 48 hours have passed from the time on this message.>> 2.  Support Ruby Quiz by submitting ideas as often as you can:>> http://www.rubyquiz.com/>> 3.  Enjoy!>> Suggestion:  A [QUIZ] in the subject of emails about the problem helps everyone> on Ruby Talk follow the discussion.>> -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=>> by Pat Eyler>> This week's quiz is a bit of a departure from the normal.  Instead of submitting> different implementations of the same code, we'd like you to submit different> implementations of the same process -- Refactoring.>> Refactoring is the art of improving the design of existing code, without> changing it's functional behaviour.  It is well documented in the book> 'Refactoring' by Martin Fowler, and on his website:>>         http://www.refactoring.com>> The quiz this week is to submit refactorings of code you use -- whether your own> code, a library or application for RubyForge, or even something from the> Standard Library.  Any submission should implement a refactoring from:>>         http://www.refactoring.com/catalog/index.html>> or other citable sources, e.g.:>>         http://kallokain.blogspot.com/2006/01/refactoring-extract-mixin.html>> Each Submission should follow this outline:>>         Refactoring name (and citation if needed)>         Original code>         Explanation of the purpose and mechanics of the refactoring>         New code>         (optionally, unit tests created/used to verify the code)>> Submissions will be combined into an online catalog of Ruby Refactorings,> probably on the RubyGarden wiki.>>
Extract method calls (http://www.theserverside.com/articles/article.tss?l=AspectOrientedRefactoringPart1)
Consider a cache that needs to be kept up to date.For example, primitives in a complex vector scene:redrawing the scene takes a relatively long time,degrading framerate, but not redrawing the scenewhen something changes is even worse. So I needto detect when the cached rendering is outdated.
To do this, I keep track of the time of the lastrendering, and compare the vector object mtimesto it. If a mtime is more recent than the last rendertime, the scene needs to be redrawn.

Original code:
class VectorObject  attr_reader :x, :y, :z, :path, :stroke, :fill, :mtime
  def x= x    @x = x    @mtime = Time.now  end
  def y= y    @y = y    @mtime = Time.now  end
  def z= z    @z = z    @mtime = Time.now  end
  def path= pt    case pt    when Path      @path = pt    when Array      @path = Path.new(pt)    end    @mtime = Time.now  end
  ... and so on for stroke and fill
end

Detecting repetition, it makes code fragile and a pain to edit.First pass, refactor `@mtime = Time.now` out to a method:
class VectorObject  attr_reader :x, :y, :z, :path, :stroke, :fill, :mtime  def touch!    @mtime = Time.now  end
  def x= x    @x = x    touch!  end
  ... similarly for other accessorsend

A bit better. Still quite unwieldy though.But! Metaprogramming to the rescue!Alias the original methods to _non_touching andreplace them with a version that calls the originalmethod, followed by touch!, and returns what theoriginal method returned:
class VectorObject  def self.touching! *mnames    mnames.each{|mn|      alias_method "#{mn}_non_touching", mn      define_method(mn){|*args|        rv = __send__ "#{mn}_non_touching", *args        touch!        rv      }    }  end
  attr_reader :mname  attr_accessor :x, :y, :z, :path, :stroke, :fill  touching! :x=, :y=, :z=, :path=, :stroke=, :fill=end

Finally, move #touching! out from the class and into a module:
module Touchable  def touching! *mnames    mnames.each{|mn|      alias_method "#{mn}_non_touching", mn      define_method(mn){|*args|        rv = __send__ "#{mn}_non_touching", *args        touch!        rv    }  endend

class VectorObjectextend Touchable  attr_reader :mname  attr_accessor :x, :y, :z, :path, :stroke, :fill
  def path= pt    case pt    when Path      @path = pt    when Array      @path = Path.new(pt)    end  end
  touching! :x=, :y=, :z=, :path=, :stroke=, :fill=end

Now we can use attr_accessor for creatingthe simple methods, override #path= witha magical setter, and not type touch! a lot.