Skip to navigation

instance_eval brings sexy back

Published November 29, 2006

Accessing regex matches can often create ugly code.

For example, take the following code:


time_components = /(\d+):(\d+):(\d+)/.match("17:00:34")
time_components[1] # => "17"
time_components[2] # => "00"
time_components[3] # => "34"

That’s not too bad in this trivial example, but if you start to use the regex Match object multiple times it starts to create ugly code. “1” isn’t exactly the most descriptive label for that chunk of data is it?

Marcel’s funky post on projectionist showing a sexy use of just-in-time methods using instance_eval and class << has had me thinking lately, and I refactored some code yesterday to make these regex’s a bit more sexy.


time_components = /(\d+):(\d+):(\d+)/.match("17:00:34")
time_components.instance_eval do
  def hours; self[1] end
  def minutes; self[2] end
  def seconds; self[3] end
end

You can now just refer to the components by what they actually represent:


time_components.hours # => "17"
time_components.minutes # => "00"
time_components.seconds # => "34"
time_components.class # => MatchData

How sexy is that? And look mah, it’s still a MatchData object.

You can make it even sexier and break it down to a single statement by making instance_eval return self:


time_components = /(\d+):(\d+):(\d+)/.match("17:00:34").instance_eval do
  def hours; self[1] end
  def minutes; self[2] end
  def seconds; self[3] end
  self
end

time_components.hours # => "17"

In the app I’m working on we’re calling out to the system to get some stats, and then munging that data to provide information to the view. Combining the above techniques I ended up with:


def memory
  @memory ||= begin
    system_sysctl.instance_eval do
      def values; split.map(&:to_i) end
      def total; values.inject(0) { |v, total| total + v } end
      def free; values.last end
      def used; total - free end
      def percent_used; (used.to_f / total.to_f) * 100 end
      self
    end
  end
end

def system_sysctl
  `sysctl ...`
end

And now my view can simply refer to the following:

@system.memory.percent_used

Hot, no?

Archived comments

Comments were previously allowed on articles. Though no new comments are being accepted you can see the old comments below.

  1. James Hill

    mmm.. instance_eval is sexy i just used it a while back for a nearly identical use.. adding a yearweek method similar to sql’s yearweak to a date object.

    mmm.. instance_eval is sexy i just used it a while back for a nearly identical use.. adding a yearweek method similar to sql’s yearweak to a date object.date.instance_eval do
    def yearweek
    self.cwyear.to_s + sprintf(“%02d”, self.cweek)
    end
    end

  2. James Hill

    if only it was formatted!!!

  3. Tim Lucas

    If only ;)

  4. James Hill

    thanks mate : D

  5. Myles Byrne

    Nice work tim. Doing an index access on matchdata objects has always bugged me too.

    You could also give the matchdata class some love directly:

    
    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
    

    and then do:

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

    Now that, is hot.

    How about adding matchnames to NilClass and have it return nil, so you can still keep the sexy chaining if it doesn’t match the string.

  7. Chris

    hot hot hot.

    (Tim + Myles) += 1

  8. Peter Cooper

    I’ve posted more about this, along with an implementation for String#match (my preferred poison) at http://www.rubyinside.com/improving-stringmatch-with-instance_eval-315.html

    Thanks guys!

  9. Johan Tam

    syntax error, unexpected ‘;’
    def values; split.map(&;:to_i) end

    An error came out while running your code above, what exactly does "&;:to_i’ mean then?

  10. Tim Lucas

    Whoops. Johan: problem with RedCloth! Sorry…

Thoughts

toolmantim

I’m Tim Lucas, a user experience developer currently in Sydney Australia.

I occasionally write, snap photos, present on various technical topics, tweet my going-ons, share teh codes and post tidbits to the scrapbook.

Most recently I published Simplifying ticket sales on sydneyoperahouse.com (February 16, 2010)

Work with me via Agency Rainford, or shoot an email to and say hello.

Powered by salt water