I'm using ERB to generate a set of instructions with location data, and want to accept an offset in my input. When I try to check if that offset is present however, it sets it's value to nil. If anyone can help explain what's going on it'd be greatly appreciated!
Here's the code I ran to test this, once I'd narrowed down the problem:
<%
# apply a default value of 0 to the offset hash
p offset.nil?
p offset
p offset.nil?
p offset
if offset.nil?
p "I ran the code inside the if statement"
offset = {}
p "offset has been initialized, since it was nil"
end
p offset.nil?
p offset
%>
and the output:
false
{"z"=>-10}
false
{"z"=>-10}
true
nil
If I delete the if statement, I get the expected result of:
false
{"z"=>-10}
false
{"z"=>-10}
false
{"z"=>-10}
but having that if statement (or one like it) is kind of important. I've figured out a workaround with a begin-rescue block that basically does what the If was supposed to do, but I'm very curious why this was happening.
I assume that at the beginning, when calling offset
you are calling a method which returns the {"z"=>-10}
value, e.g. method in an included helper module.
Now, in Ruby local variables have precedence before methods with the same name. Thus, if there is a local variable named offset
, when referencing this name, you are getting the value of that variable rather than the result of calling the method of the same name.
Now, in Ruby when you define any code which may set a variable, this variable is initialized with nil
, even if the code which sets an initial value is never actually run. This is similar to variable hoisting in JavaScript (but not entirely):
defined?(some_variable)
# => nil
if false
some_variable = 'value'
end
defined?(some_variable)
# => "local-variable"
Thus, because you set offset = {}
in the body of your if block, afterwards, you have a local offset
variable in existence which shadows the offset
method.
If you want to always call the offset
method, you can instruct Ruby to call the method by adding parentheses (which are otherwise optional)
p offset
# => {"z"=>-10}
if offset.nil?
offset = {}
end
p offset
# => nil
p defined?(offset)
# => "local-variable"
p offset()
# => {"z"=>-10}
p defined?(offset())
# => "method"
To implement your actual use-case, you could also use something like this instead of your if
statement in order to set the offset
variable as either the result of the offset
method or an empty hash if the method returns nil
or false
:
offset = offset() || {}