1

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.

14
  • are you sure you have if offset.nil? and not something like if offset = nil Commented Jun 14, 2024 at 20:24
  • Triple check that is the code you're actually executing. Commented Jun 14, 2024 at 20:26
  • if offset == nil gives the same result; and after triple checking, that is the code I am executing Commented Jun 14, 2024 at 20:36
  • sorry @Alex I misread your question, I'm using if offset.nil? exactly, no typo Commented Jun 14, 2024 at 20:38
  • 1
    @DanielCusumano can you create a minimal example that reproduces the problem? stackoverflow.com/help/minimal-reproducible-example Commented Jun 14, 2024 at 21:27

1 Answer 1

4

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() || {}
Sign up to request clarification or add additional context in comments.

3 Comments

That makes sense, and your implementation works perfectly. Thanks for the answer!
@DanielCusumano If you found the answer helpful, you may consider accepting it.
done! Sorry I'm new to stack overflow

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.