Exploiting obscure Ruby quirks for fun and profit

Here’s one quirk I accidentally discovered in Ruby. Check this out (this is copied from my irb session):

>> defined? x
=> nil
>> x
NameError: undefined local variable or method `x' for main:Object
	from (irb):2
>> if false
>>   x = 3
>> end
=> nil
>> defined? x
=> "local-variable"
>> x
=> nil

What’s going on here?

I first checked if x variable was defined — it was not. And trying to access it resulted in an exception. Then there is an if false block that obviously is not executed.

And now for the strange part: the result of defined? x is now — quite shockingly for me — a "local-variable". Wait a minute — was the if block executed somehow?! No, it was not: the value of x is nil, not 3. Wait a (second) minute — the value of x? Wasn’t there an exception when I tried to access it last time? What is happening here?

Well, I’m not going into Ruby’s implementation details here (because I don’t know them and didn’t bother to check). I’ll just assume that what’s happened means that merely parsing the block resulted in allocating the place in some symbol table for x and now we can use it as any other local variable. BTW I checked and it works for MRI’s Ruby versions 1.8.6, 1.8.7 and 1.9.1 so I think it’s just an obscure feature, not a bug that may disappear in future versions.

But what use of such “feature”? — you may ask. Well, one could simplify conditional expressions with variable assignments. For example, instead of:

def display_address some_model
  if some_model.address.street.present?
    str = some_model.address.street
  else
    str = nil
  end
  if some_model.address.street_number.present?
    num = some_model.address.street_number
  else
    num = nil
  end
  "#{str} #{num}"
end

one could write:

def display_address some_model
  if some_model.address.street.present?
    str = some_model.address.street
  end
  if some_model.address.street_number.present?
    num = some_model.address.street_number
  end
  "#{str} #{num}"
end

That’s right — you can skip the else‘s and it will work just the same.

Of course, same goes for other control statements (unless, while, for etc.), for any level of nesting and for statement modifiers, too. Example:

>> for a in []
>>   unless true
>>     while false
>>       b = 7 if "maybe" == "sometimes"
>>     end
>>   end
>> end
=> []
>> defined? b
=> "local-variable"
>> b
=> nil

That’s all, folks. Happy simplifying :)


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: