instance_eval brings sexy back
November 30, 2006 10:04 (Sydney Australia)
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?

Comments
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.
James Hill
if only it was formatted!!!
Tim Lucas
If only ;)
James Hill
thanks mate : D
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:
and then do:
Tim Lucas
Now that, is hot.
How about adding
matchnamesto NilClass and have it return nil, so you can still keep the sexy chaining if it doesn’t match the string.Chris
hot hot hot.
(Tim + Myles) += 1
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!
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?
Tim Lucas
Whoops. Johan: problem with RedCloth! Sorry…
To comment on this article you must have javascript enabled.