I have a Ruby file named test.rb
ff="ff"
def test
puts ff
end
I execute it, got error:
test.rb:3:in `test': undefined local variable or method `ff' for main:Object (NameError)
What's the reason for this? Is there any documentation to explain it?
A local variable is only accessible within the block of its initialization. Local variables are not available outside the method. There is no need to initialize the local variables. Instance Variables: An instance variable name always starts with a @ sign.
Local Variables We can put it in a method body. We declared num in the method body of main(). Variables declared inside a method are called local variables. Local variables can only be used within the method body.
Well no, it wouldn't. Local variables (declared within a method) only exist for the lifetime of that method call, within the method.
Here is an example showing the usage of global variable. NOTE − In Ruby, you CAN access value of any variable or constant by putting a hash (#) character just before that variable or constant.
The reason ff
is inaccessible inside the test
method definition is simply that methods (created with the def
keyword) create a new scope. Same with defining classes and modules using the class
and module
keywords respectively.
The role of main
(the top-level object) is almost completely irrelevant to the question of scope in this situation.
Note that, if you DO want your test
method to have access to locals defined in the definition context, then use the define_method
(or in your case, the define_singleton_method
method), see here:
ff = "hi"
define_singleton_method("test") { ff }
test #=> "hi"
Unlike the def
keyword, the define_method
family of methods do not create new scopes but instead close over the current scope, capturing any local variables.
The reason using @ff
worked in the next example given by @soup, is not that main
is somehow a "special case" it's just that an ivar defined at top-level is an ivar of main
and so is accessible to a method invoked on main
.
What, however, is the relationship of the test
method to main
? It is not a method on just main
itself - it is actually a private instance method defined on the Object
class. This means that the test
method would be available (as a private method) to nearly every object in your ruby program. All methods defined at top-level (main
) are actually defined as private instance methods on the Object
class.
For more information on the Ruby top-level, see this article: http://banisterfiend.wordpress.com/2010/11/23/what-is-the-ruby-top-level/
Ruby scope is both simple and complex.
First you have to remember that everything is an object and everything has a scope.
To answer your question directly, main
is an object and such when you write def x...
you're defining a method on the object main
Observe in pyr/irb:
# note the error describes
[1] pry(main)> main 'main:Object'
NameError: undefined local variable or method 'main' for main:Object
from (pry):1:in '<main>'
# self is the current object, which is main
[2] pry(main)> self
=> main
# note main is an object of type Object
[3] pry(main)> self.class
=> Object
# main has methods
[4] pry(main)> self.methods
=> [:to_s, :public, etc etc]
So when you write
ff = "ff"
def test
puts ff
end
what you're really doing is
class main
ff = "ff"
def test
puts ff
end
end
Which doesn't work because ff
is out of scope. To fix this you have to turn ff
into an instance variable, so prepend its name with @
. The following will work:
@ff = "ff"
def test
puts @ff
end
test
Note that this seems to be a special case for main
than regular classes, see below.
If we have our own test class:
class Test1
ff = "ff"
def test
puts ff
end
end
Test1.new.test # undefined variable/method 'ff' error
This fails because ff
is not defined in the correct scope as expect. It is instead scoped to when the parser is executing our class declaration.
So lets try the fix above:
class Test1Fixed
@ff = "ff"
def test
puts @ff
end
end
Test1Fixed.new.test # results in blank line
Thats weird.
Lets add this method:
def ff?
puts "ff is: #{@ff.nil? ? "nil" : "not nill"}"
end
Test1Fixed.new.ff? # => ff is: nil
Why is @ff nil?
The following might make this more clear:
class Test2
puts "Where am i?"
@instance = "instance"
def initialize
puts "Initalize"
end
puts "pants on"
def test
puts "This will NOT output 'instance': #{@instance}"
end
def self.class_test
puts "This WILL output 'instance': #{@instance}"
end
end
puts "Creating new Test2 Object"
t = Test2.new
puts "Calling test on Test2 Object"
t.test
puts "Calling class_test on Test2 Class object"
Test2.class_test
Running this we get the following output:
$ ruby scope.rb
Where am i?
pants on
Creating new Test2 Object
Initalize
Calling test on Test2 Object
This will NOT output 'instance':
Calling class_test on Test2 Class object
This WILL output 'instance': instance
As you can see, the interperter runs over our class declaration in order, just like anywhere else and outputs our puts
statements. This gets more interesting when we start calling methods. Writing @instance = ...
is the same as writing self.instance = ...
, so can you guess where we've defined instance? Yep, on the Test2 class object (not a Test2 object).
This is why when we call test
, nothing is outputed, because inside test
, self
refers to the instanciated Test2
object, not the Test2 class object
(which is where we set @instance to be anything!). This is why you setup your instance variables inside initialize
, where self
will point to the actual object.
You can see when we define a class method via self.class_test
and then call that method on the Test2 class object (Test2.class_test
), we get the expected output, because in that scope, @instance
was defined.
Hope this made some sense.
The http://rubykoans.com/ scope section may help solidify some of this knowledge.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With