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.