In Ruby, there are 5 scopes:
- script scope
- module scope
- class scope
- method scope
- block scope
Block scopes nest, the other ones don't. Blocks can close over their lexical environment, the other ones can't. (IOW: not only do they nest inside their lexically surrounding environment, i.e. can access variables from their lexically surrounding environment, they can even continue to do so after that surrounding environment ceases to exist.)
Unlike some other languages, Ruby doesn't have a top-level or global scope for local variables. The "biggest" scope is script scope, but that isn't global, it is confined to a single script. (Usually, a script is the same as a file, but there are Ruby implementations that don't use files, so a term like "file scope" would be misleading.)
Local variables are defined from the point on where their definition is parsed, and initialized from the point on that their definition is executed. In between, when they are defined but not initialized, they evaluate to nil.
Consider this slightly modified example:
if false hi = 'hello' end hi # => nil # hi is defined here, because its definition was parsed if true hi = 'olleh' end hi # => 'olleh' # hi is initialized here, because its definition was executed