Ruby’s surprising handling of local variables

I was trying to read some Rails code today and came across the definition of the link_to view helper, which looks like this:

def link_to(*args, &block)
  if block_given?
    options      = args.first || {}
    html_options = args.second
    concat(link_to(capture(&block), options, html_options))
  else
    name         = args.first
    options      = args.second || {}
    html_options = args.third
 
    url = url_for(options)
 
    if html_options
      html_options = html_options.stringify_keys
      href = html_options['href']
      convert_options_to_javascript!(html_options, url)
      tag_options = tag_options(html_options)
    else
      tag_options = nil
    end
 
    href_attr = "href=\"#{url}\"" unless href
    "<a #{href_attr}#{tag_options}>#{name || url}</a>"
  end
end

At first glance, I thought for sure I had found a bug! The variable, href, is only initialized if html_options is specified. It seems like the “href_attr = … unless href” line would blow up otherwise, since it’s testing a variable that may not have been set. Or, so I thought. It turns out that my understanding of Ruby’s local variable semantics was wrong, as demonstrated by this simple test:

irb(main):001:0> x
NameError: undefined local variable or method 'x' for main:Object
        from (irb):1
        from :0
irb(main):002:0> if false
irb(main):003:1>   x = 0
irb(main):004:1> end
=> nil
irb(main):005:0> x
=> nil

It seems that assigning to an uninitialized variable in code that does not execute is sufficient to create that variable and assign it a default value of nil. This is in contrast to Python, which doesn’t define new variables unless the code that sets them actually executes:

>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
>>> if False:
...   x = 0
...
>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined

Neither does Javascript:

js> x
typein:1: ReferenceError: x is not defined
js> if (false) x = 0;
js> x
typein:3: ReferenceError: x is not defined

Is it just me, or is Ruby’s behavior completely bizarre here?