Space Vatican

Ramblings of a curious coder

Ways in Which the Console Tricks You

IRB, and by extension Rails’ script/console is an incredibly useful tool. Being able to explore your objects or an API without having to write and compile a test program each time is absolutely priceless. There are however a few cases where the console can lead you astray.

When you evaluate something in the console it will display the result of calling inspect on whatever your statement(s) returned. Inspect is just a function so it can lie to you, be selective about what it shows or even have side effects.

Prior to Rails 2.0 the inspect function on ActiveRecord::Base dumped pretty much everything known about the object. This could get a bit cumbersome quite easily. For example if an association was loaded then the dump of all those objects would be included in the output taking up pages and pages and adding very little value. Starting with Rails 2.0 only the value of each attribute is listed:

1
2
>> Conversation.find :first
=> #<Conversation id: 2, title: nil, created_at: "2008-07-22 00:12:24">

Rails doesn’t list all attributes however: it knows which columns exist on that table and uses that information to destroy only those attributes. This often confuses people who are using :select to add attributes as it looks as though attributes aren’t there. Oh ye of little faith who do not believe until they have seen!

1
2
3
4
>> c = Conversation.find :first, :select => "*, 'hello world' as bonus_attribute"
=> #<Conversation id: 2, title: nil, created_at: "2008-07-22 00:12:24">
>> c.bonus_attribute
=> "hello world"

The extra piggy backed attribute was there all along, ActiveRecord’s implementation of inspect just chose not to show it to you. In theory an object’s implementation of inspect could do anything it wants. Usually it will be trying to be helpful, but that’s not to say it won’t mislead you.

Side effects

Every now and again the following pops up on one the the rails mailing lists or IRC channels:

1
2
>> conversation.messages
=> [#<Message id: 1, body: nil, conversation_id: 2, created_at: "2008-07-22 00:12:30"]

ZOMG! Just doing that loaded the association (or the all objects in the named scope), so doing conversation.messages.find… will load the collection before doing the find. What a waste! If only it were actually true.

Association proxies are slippery things. You may have already noticed that they claim to be arrays, look like arrays and yet clearly aren’t as they have a whole bunch of extra methods (which aren’t singleton methods). A full discussion of association proxies is something there’s no time for today, but the basic idea is that they implement specific methods like find (do a find that is scoped to the association) or count (does a SELECT COUNT(*) FROM …). If anything else happens (ie if method_missing is hit) then they load the association and propagate the called method to the target array.

When I typed in conversation.messages the relevant methods were called and an instance of an association proxy was returned. IRB then wanted to display it and so called inspect. Association proxies don’t implement that method and so the collection was loaded and inspect called on that. In order words conversation.messages doesn’t actually load the collection, it’s what IRB did with it afterwards that did. In a standalone script or program this wouldn’t happen at all. Pheww, Rails isn’t stupid.

There’s a little trick you can use in cases like this when want to stop irb inspecting something that will have a side effect such as the one described above or because the output from inspect would be very long:

1
x= (1..100000).to_a; false

Instead of printing several pages of the numbers from 1 to 100000 this just prints false. When irb evaluates that line the last statement is false (you could put anything you want there) and so the return value from that is what irb calls inspect on.

Fun with local variables

Try the following snippet:

1
2
eval("a=1")
puts a

If you paste it into IRB it works just fine, but if you paste it into a script and run that then it won’t work (you’ll get a undefined local variable or method ‘a’ ). Local variable scope is one of those things that are just a little bit fiddly in ruby. For example local variables created by eval are only visible from subsequent calls to eval. The big difference here is that ruby loads and parses your script in one go whereas irb does it line by line. The pickaxe has some more discussion on this.