Why does a locally scoped variable that hasn't been defined reference the instance variable of the same name?

StackOverflow https://stackoverflow.com/questions/7102348

Question

I came across an odd bug in my code that revealed an interesting behavior of ruby. Hopefully someone can explain why it behaves this way.

I had a class with an instance variable @foo and a method that referenced a locally scoped variable foo. I refactored part of the method out accidentally leaving the reference to foo; the variable no longer defined in the scope. It ended up pointing to @foo instead. Changing foo made changes to @foo and vice versa.

Simplified version: EDIT : added ImOutOfNames.

class ImOutOfNames
    attr_accessor :foo # the culprit!
end

class Bar < ImOutOfNames
    def initialize
        @foo = "that is a tasty burger"
    end

    def bar_method_1
        foo = "Come on Yolanda, whats Fonzie like?"
        bar_method_2
    end

    def bar_method_2
        puts foo
    end
end

And the output of bar_method_1 and bar_method_2 was "that is a tasty burger". I was expecting there to be an error, for example running the above code gets

NameError: undefined local variable or method

I even had a more senior developer come take a look and he was somewhat baffled and confirmed the behavior.

Is this an expected behavior and I misunderstood how @variables work or is there something wrong?

Was it helpful?

Solution

Your previous buggy code probably at an attr_accessor definition that created a method foo that accessed to your instance variable,

You can have the same behavior if your code is like this:

class Bar

  attr_accessor :foo

  def initialize
    @foo = "that is a tasty burger"
  end

  def bar_method_1
    foo = "Come on Yolanda, whats Fonzie like?"
    bar_method_2
  end

  def bar_method_2
    puts foo
  end
end

The attr_accessor call defines two methods in your object;

def foo
  @foo
end

def foo=(value)
  @foo = value
end

So in your case, when no local variable was defined, the method was used, but as you did not call attr_accessor in this example you posted the method was not defined and there was no local variable to use on bar_method_2 so the call fails.

OTHER TIPS

Do you have an attr accessor for your @foo instance variable? That's one way this could happen.

I'd recommend not using the same name for a locally scoped variable, in general, but you can find out if it's coming from some method with

self.method(:foo)

or if it's even defined with

defined? foo

Using the ruby runtime to your advantage definitely reduces the mysticism and magical nature of some code.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top