Question

Short story:

"Why does defined?(59[0][:whatever]) evaluate to true?"


Long story:

I came across some strange behaviour lately which threw me off.

I was developing a method that did some washing of the data:

#Me washing input data:
def foo(data)
  unless data && defined?(data[0]) && defined?(data[0][:some_param])
    method2(data[0][:some_param])
  else
    freak_out()
  end
end

I usually write tests where I spew in all kinds of junk data to make sure nothing strange happens:

describe "nice description" do
  it "does not call method2 on junk data" do
    expect(some_subject).to_not receive(:method2)
    random_junk_data_array.each do |junk|
      some_subject.foo(junk)
    end
  end
end

Well, method2 was called here. It happened when junk was a fixnum.

I am using ruby 2.1.0, and I can see that Fixnum has a #[] method which fetches the bit at that position, nice.

But why is fixnum[0][:some_param] considered to be defined?

Was it helpful?

Solution

defined? expression tests whether or not expression refers to anything recognizable (literal object, local variable that has been initialized, method name visible from the current scope, etc.). The return value is nil if the expression cannot be resolved. Otherwise, the return value provides information about the expression.

Let me explain with an example :-

defined?("a") # => "expression"
# this returns "method", as there is a method defined on the class String. So, the
# method invocation is possible, and this is a method call, thus returning `"method"`.
defined?("a".ord) # => "method"
# it return nil as there is no method `foo` defined on `"a"`, 
# so the call is not possible.
defined?("a".foo) # => nil

Now coming to your point :-

As you said data[0] gives a Fixnum instance, and of-course Fixnum#[] exist. Thus fixnum_instance[:some_param] is also a valid method call. It just testing if the method is defined or not. If defined, it will tell yes this is a "method" expression. Otherwise nil. Not actually will check if the method call succeeded or not.

In Ruby all objects has truthy values except nil and false, thus "method" being a string object also has the truthy value, thus your condition got succeeded.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top