Reputation: 23
I am a newbie at Ruby and have a question with the defined? keyword.
Here's a snippet of code that I've written to load a yaml file to initialize settings in my Ruby script:
# Read settings file
require 'YAML'
settingsFile = File.join(File.dirname(__FILE__), "settings.yml").tr('\\', '/')
Settings = YAML.load_file(settingsFile) unless defined? Settings
puts Settings
The yaml file looks like this:
Hello: World
This outputs correctly with:
{"Hello"=>"world"}
Now if I use a variable instead of a constant to store the settings, such as the following:
# Read settings file
require 'YAML'
settingsFile = File.join(File.dirname(__FILE__), "settings.yml").tr('\\', '/')
settings = YAML.load_file(settingsFile) unless defined? settings
puts settings
settings returns empty.
What gives? Why would using a constant make this work?
Upvotes: 2
Views: 46
Reputation: 211580
This is a quirk in the way Ruby handles trailing if
/unless
conditions and how variables come into existence and get "defined".
In the first case the constant is not "defined" until it's assigned a value. The only way to create a constant is to say:
CONSTANT = :value
Variables behave differently and some would argue a lot more strangely. They come into existence if they're used anywhere in a scope, even in blocks of code that get skipped by logical conditions.
In the case of your line of the form:
variable = :value unless defined?(variable)
The variable
gets "defined" since it exists on the very line that's being executed, it's going to be conditionally assigned to. For that to happen it must be a local variable.
If you rework it like this:
unless defined?(variable)
variable = :value
end
Then the behaviour goes away, the assignment proceeds because the variable was not defined prior to that line.
What's strange is this:
if defined?(variable)
variable = :value
end
Now obviously it's not defined, it doesn't get assigned, but then this happens:
defined?(variable)
# => "local-variable"
Now it's defined anyway because Ruby's certain it's a variable. It doesn't have a value yet, it's nil
, but it's "defined" as far as Ruby's concerned.
It gets even stranger:
defined?(variable)
# => false
if (false)
variable = :value
end
defined?(variable)
# => "local-variable"
Where that block of code didn't even run, it can't even run, and yet, behold, variable
is now defined. From that assignment line on forward that variable exists as far as Ruby is concerned.
Since you're doing an attempted assignment and defined?
on the same line the variable exists and your assignment won't happen. It's like a tiny paradox.
Upvotes: 7