Variables are serious business. You can wink and nudge them whilst making inappropriate jokes, and sure, they’ll laugh along awkwardly, but at the end of the day they’ve much responsibility.
Because they are so serious, I will make this brief.
The first thing worth noting is indeed a common Ruby fact: undefined global and instance variables have a value of nil. I don’t know about you, but I (as a young Rubista) expected all undefined variable access attempts to raise an exception.
>> @what_it_is_jive_turkey => nil >> what_it_is_jive_turkey NameError: undefined local variable or method `what_it_is_jive_turkey'
Where this really becomes apparent is in Rails’ partials. You can throw around @counter + 1 if @counter all you want, but for local variables it’s a bit less succinct. Try counter + 1 if defined? counter.
There’s also this little gem:
>> not_defined NameError: undefined local variable or method `not_defined' >> not_defined = not_defined => nil >> not_defined => nil
Ruby continues to amaze. The Pickaxe’s explanation of local variables vs methods helps unravel the mystery. You see, Ruby keeps track of which local variables have been assigned so far in scope so that it can tell the difference between local variables and methods without seeming silly. My guess is the above’s a perfect example of this: Ruby remembers that not_defined was assigned something when it hits the right side of the assignment operation. When it checks to see what not_defined’s value is, it finds nothing, so returns nil. Hence, not_defined is assigned nil. (I could be wrong.)
While we’re here, remember that blocks have strangeness in their scoping. They are greedy and, if they can, steal variables from the enclosing scope.
>> counter = 0 => 0 >> 0.upto(100) { |counter| print counter + 1 } 12345678910... => 0 >> counter => 100
The block did not make its own scope-local variable counter but instead used the enclosing scope’s counter, assigning it a new integer each step of the way.
This also applies to instance and global variables.
>> @counter = 0 => 0 >> 0.upto(100) { |@counter| print @counter + 1 } 12345678910... => 0 >> @counter => 100
Word is Matz is not happy with this part of Ruby and it will be removed in 2.0.
Of course, blocks refuse to add new variables to the enclosing scope. They take and they take, but, in the true spirit of Christmas, refuse to give:
>> counted NameError: undefined local variable or method `counted' >> 0.upto(100) { |counted| print counted + 1 } 12345678910... => 0 >> counted NameError: undefined local variable or method `counted'
That’s it. Don’t forget.
I am about half way through Ruby for Rails right now, which offers pretty good insight into this type of issue. For me the Pickaxe is better as a reference. It just isn’t a great read from cover to cover… too much stuff I already know. Ruby for Rails, on the other hand, is a lighter read and seems to cover a lot of these need-to-know issues in a succinct fashion. Like the fact that a bareword to the left of an assignment operator is ALWAYS a local variable, and anywhere else is either a local variable or a method in that order.
I don’t mean to dis pickaxe… it’s definitely my top reference book at the moment, but I highly recommend Ruby for Rails for any serious programmer looking to quickly get on top of Ruby’s syntax and quirks.
“Word is Matz is not happy with this part of Ruby and it will be removed in 2.0.” I have had to use local variables within blocks a lot so I could see what there value was after because like you said, they take but don’t give…. If blocks in 2.0 will be that strict with scope how would you handle the type of situations that you would like to get something back?
You can use this to set a default value in a partial for a local variable, so the local variable only needs to be set if you want to override the default behaviour. Something like this: <= (small ||= nil) &x%x ” small-star” %> which will output the text ” small-star” (which I’m using in a CSS class) if small is defined as true. If it’s defined as false, or undefined an empty string will be output.
Often I have used instance variables inside of partials in order due to the very fact they default to nil when not set. Am I a bad person? :-)
Just found this after being bitten by undefined foo in a Rails partial. You win again.
Chime in.