Ruby Weekly is a weekly newsletter covering the latest Ruby and Rails news.

Improving String#match with instance_eval

By Peter Cooper / December 3, 2006

A couple of days ago, Tim Lucas wrote a cool article called "instance_eval brings sexy back" where he demonstrated how to use instance_eval to improve the usability of the match method. The downside, however, was that Tim's technique required manually defining accessor methods each time match was used.

Myles Byrne rapidly responded with a cuter solution:

class MatchData
  def matchnames(*names)
    names.each_with_index do |name, index|
      self.instance_eval "def #{name}; self[#{index+1}] end"
    end
    self
  end
end

time_components = /(\d+):(\d+):(\d+)/.match("17:00:34").matchnames(:hours, :mins, :secs)
time_components.hours

I quickly realized, however, that I never use match in this way. I prefer to use String#match instead (Regexp#match doesn't click to me, it seems back to front). I also wasn't too keen on the method chaining as it didn't address instances where the result of match is nil. I, therefore, have come up with an uglier, but more pragmatic solution:

class String
  alias _match match
  def match(*args)
    m = _match(args.first)
    if m && m.length > 1
      args[1..-1].each_with_index do |name, index|
        m.instance_eval "def #{name}; self[#{index+1}] end"
      end
    end
    m
  end
end

m = "12:34:56".match(/(\d+):(\d+):(\d+)/, :hour, :minute, :second)
puts m.hour
puts m.minute
puts m.second

Thanks to Tim and Myles for the inspiration on this.

Comments

  1. Rimantas says:

    This should be "12:34:56".match(/(\d+):(\d+):(\d+)/
    My cryptic version: http://pastie.caboo.se/25525

  2. Tim Lucas says:

    Nice, though I do prefer Myles less magical solution (and handling matchnames in NilClass too). Is there a way to add a singleton method to the nil returned by match, rather than messing up the whole NilClass class? Because nil is a singleton object it makes life a bit more difficult.

  3. Greg says:

    Let's clean that up a bit, and avoid using eval. If you're going to do meta-programming and mess with an object's metaclass (a.k.a. singleton class) it makes sense to use it explicitly.

    class String
    alias _match match
    def match(*args)
    m = _match(args.shift)
    if m && m.length > 1
    meta = (class

  4. Greg says:

    Grr, my comment got cut off and reformatted. Try this: http://pastie.caboo.se/25533

  5. Peter Cooper says:

    Rimantas: I can safely blame WordPress for this. It always seems to mangle backslashes in my code and I have to go back and manually add them! Thanks for pointing it out.

    Tim: To be honest, I just don't like the tacked on method. It reads a bit longer. It's really something match should provide out of the box (IMHO), so that's why I prefer it there. That said, perhaps there is wider scope for that technique on Arrays generally (not just MatchData).. so you can assign method names to different elements.

    Greg: Excellent, I like this one the best :)

  6. Tim Fletcher says:

    http://tfletcher.com/lib/named_captures.rb

    Another way :)

  7. Daniel Berger says:

    Oniguruma already has named captures. :)

  8. Tim Lucas says:

    Tim: Nice, and I love the quality and self-contained tests. I'll aspire to similar quality next time I post some code.

  9. Paolo Dona says:

    Hey guys, any chance to have this included in some gem (like facets or similar)?
    Wouldn't be great to collect all these tricks?

Other Posts to Enjoy

Twitter Mentions