Tuesday, December 2, 2014

HookLyingSyncer: gem to keep method_missing and respond_to_missing? in sync

   A while back, I wrote about the need to keep method_missing and respond_to_missing? in sync.

   (A brief refresher: in 2011, Avdi Grimm wrote a blog post about that.  In the comments, I wrote up a quick and dirty hack to do so, in very restricted cases, and then a still-dirty improvement, that unfortunately has since been mangled somehow.)

   At RubyConf 2014, Betsy Haibel spoke on Ruby metaprogramming, including that same need.  That inspired me to work on the concept again, taking a different approach (essentially a decorator) that I had briefly considered in those comments.

   The result is my new gem HookLyingSyncer.  (I was going to call it FirstResponder, but that name was already taken.)  The code is at https://github.com/davearonson/hook_lying_syncer.  For now, the code looks like:
class HookLyingSyncer

  def initialize(object, matcher, &block)
    @object = object
    @matcher = matcher
    @block = block
  end

  private

  def respond_to_missing?(sym, include_all=false)
    matches = find_matches(sym)
    matches.any? ? true : @object.send(:respond_to?, sym, include_all)
  end

  def method_missing(sym, *args, &blk)
    matches = find_matches(sym)
    if matches.any?
      @block.call(@object, matches, *args)
    else
      @object.send(sym, *args, &blk)
    end
  end

  def find_matches(sym)
    result = @matcher.call(sym)
    result ? result : []
  end

end
   The tests contain some examples, with further usage explanation in the README.  Long story short, it can be used on instances and classes, to add or override method definitions, including overriding new so as to add methods to all new instances of a class.

   This is my first time actually making a gem, and I haven't done much with metaprogramming before, especially something that other people are going to use to do their metaprogramming.  So, any feedback would be greatly appreciated!