I'm struggling with variable scope in ruby. I was under the impression that local variables were accessible by methods below them. Looking at the following code however I'm getting an undefined variable error.
a = Array.new
def testing()
a.push("test")
end
testing
Using global variables it works just fine, how can I avoid using global variables?
There isn't much to say here except that local variables in Ruby are only accessible in the scope in which they are defined and any blocks (closures) defined in that scope that capture them. Since in Ruby, unlike some other dynamic languages, method are not closures, they do not capture local variables.
If you tried, say, this:
irb(main):001:0> a = 3
=> 3
irb(main):002:0> define_method(:testing) do
irb(main):003:1* puts a
irb(main):004:1> end
=> :testing
irb(main):005:0> testing
3
It works, since the code is in a block instead of a method.
Defining a method in the top-level can be quite confusing. Let's wrap your code in a class instead:
class Foo
a = []
def testing
a << 'test'
end
end
(I've shortened Array.new
to []
and a.push(...)
to a << ...
)
Foo#testing
can be called via:
foo = Foo.new
foo.testing
#=> undefined local variable or method `a'
Apparently, this doesn't work. The first a
is a local variable in the scope of the class
body, whereas the second a
is a local variable within an instance method.
Moving the variable initialization out of the class body into the initialize
method doesn't work either, because local variables are not shared across methods:
class Foo
def initialize
a = [] # <- one 'a'
end
def testing
a << 'test' # <- another 'a'
end
end
To get this working, you have to use an instance variable:
class Foo
def initialize
@a = []
end
def testing
@a << 'test'
end
end
foo = Foo.new
foo.testing
#=> ["test"]
foo.testing
#=> ["test", "test"]
You could use instance variables. Any variable whose name begins with @ is an instance variable and is available anywhere in the class or method in which it is defined. For example, the variable @A defined within class B will be available to any methods in B.
2.3.3 :007 > def testing()
2.3.3 :008?> [].push("test")
2.3.3 :009?> end
=> :testing
2.3.3 :010 > testing
=> ["test"]
You can't let local variables accessible by methods below them , you can use block like the answer by @Linuxios, or use the way that it easy work.