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.

    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…

Articles

toolmantim

I'm Tim Lucas, a user experience designer and developer currently in Sydney Australia.

I run a web application design and development company Agency Rainford, present on various technical topics, snap the occasional photo, tweet my going-ons, share teh codes and post other tid-bits to the tumble.

Most recently I published LAN hacking with Bananajour (June 11, 2009)

Shoot an email to and say hello.

Powered by vegemite