Saturday, October 24, 2009

Recipe 8.10. Getting a Human-Readable Printout of Any Object










Recipe 8.10. Getting a Human-Readable Printout of Any Object



Problem


You want to look at a natural-looking rendition of a given object.




Solution


Use
Object#inspect
. Nearly all the time, this method will give you something more readable than simply printing out the object or
converting it into a string.



a = [1,2,3]
puts a
# 1
# 2
# 3

puts a.to_s
# 123

puts a.inspect
# [1, 2, 3]
puts /foo/
# (?-mix:foo)
puts /foo/.inspect
# /foo/
f = File.open('foo', 'a')
puts f
# #<File:0xb7c31c30>
puts f.inspect
# #<File:foo>





Discussion


Even very complex data structures can be inspected and come out looking just like they would in Ruby code to define that data structure. In some cases, you can even run the output of inspect through eval to recreate the object.



periodic_table = [{ :symbol => "H", :name => "hydrogen", :weight => 1.007 },
{ :symbol => "Rg", :name => "roentgenium", :weight => 272 }]
puts periodic_table.inspect
# [{:symbol=>"H", :name=>"hydrogen", :weight=>1.007},
# {:symbol=>"Rg", :name=>"roentgenium", :weight=>272}]

eval(periodic_table.inspect)[0]
# => {:symbol=>"H", :name=>"hydrogen", :weight=>1.007}



By default, an object's inspect method works the same way as its to_s method.[3] Unless your classes override inspect, inspecting one of your
objects will yield a boring and not terribly helpful string, containing only the object's class name, object_id, and instance variables:

[3] Contrary to what ri Object#inspect says, Object#inspect does not delegate to the Object#to_s method: it just happens to work a lot like Object#to_s. If you only override to_s, inspect won't be affected.



class Dog
def initialize(name, age)
@name = name
@age = age * 7 #Compensate for dog years
end
end

spot = Dog.new("Spot", 2.1)
spot.inspect
# => "#<Dog:0xb7c16bec @name="Spot", @age=14.7>"



That's why you'll help out your future self by defining useful inspect methods that give relevant information about the objects you'll be instantiating.



class Dog
def inspect
"<A Dog named #{@name} who's #{@age} in dog years.>"
end
def to_s
inspect
end
end
spot.inspect
# => "<A Dog named Spot who's 14.7 in dog years.>"



Or, if you believe in being able to eval the output of inspect:



class Dog
def inspect
%{Dog.new("#{@name}", #{@age/7})}
end
end
spot.inspect
# => "Dog.new("Spot", 2.1)"
eval(spot.inspect).inspect
# => "Dog.new("Spot", 2.1)"



Just don't automatically eval the output of inspect, because, as always, that's dangerous:



strange_dog_name = %{Spot", 0); puts "Executing arbitrary Ruby…"; puts("}
spot = Dog.new(strange_dog_name, 0)
puts spot.inspect
# Dog.new("Spot", 0); puts "Executing arbitrary Ruby…"; puts("", 0)
eval(spot.inspect)
# Executing arbitrary Ruby…
#
# 0














No comments:

Post a Comment