Your Ruby Gotcha of the Day

Liquid error: undefined method `login' for nil:NilClass : March 8th, 2006

In #caboose, technomancy found this particularly odd gotcha in Ruby:

class RubyGotcha
  attr_accessor :arguments
  
  def initialize
    self.arguments = [1,2,3]
  end
  
  def test_bad
    if false == true
      # This makes arguments into a local, 
      # even though it is never executed.
      arguments = [4,5,6]
    end
    
    puts "#{arguments}" # prints nothing
  end
  
  def test_control
    puts "#{arguments}" # prints "123"
  end
end
Why is there a difference? Florian Gross (aka flgr @ #ruby-lang) explains that this is because Ruby decides if a keyword is a local at parse time, not at runtime. This is a very subtle gotcha. Code that is never actually executed can affect the output of your code. Keep it in mind. This is especially likely to be confusing in ActiveRecord, since it makes heavy use of method_missing, making the correct behavior somewhat hard to guess.

8 Responses to “Your Ruby Gotcha of the Day”

  1. josh Says:
    This is why I'm partial to the explicit reference to self instead of the implict. I guess it's my Smalltalk upbringing, but I like to see self.message spelled out for me.
  2. Stefan Says:
    Josh, You aren't the only one.
  3. Phil Says:
    See, I thought it was just related to method_missing all along. It turns out it's rather more... sinister.
  4. Peter Burns Says:
    Yeah, I've been bitten by a similar issue recently myself. I was working on a domain specific language, and I wanted the syntax to be:
    graph = FunctionGrapher.graph {
      title = "Sort Functions"
      ...
    }
    
    And I kept wondering why the title= method wasn't being called until I thought it out and realized that it was defining a variable. I've settled with
      title "Sort Functions"
    
    for now, rather than
      self.title= "Sort Functions"
    
    which isn't as elegant to me. I'm not 100% satisfied with it though.
  5. KyleMaxwell Says:
    Peter: Why not use the hash style? title => "Sort functions",
  6. rickbradley Says:
    This is the bit that's discussed in the Pickaxe (2nd edition) pp. 329-330 in the section "Variable/Method Ambiguity" (pp. 212-3 in the 1st edition). Seems like there's real potential for "action at a distance" with this -- if you open a class or module remotely, depending on where in the inclusion chain your code resides you could turn some name in a later opener of that module into a variable or a function. Then again, might be handy in an obfuscated ruby contest. That true==false idiom is particularly cute looking for such purposes. Hmmm... sounds like a branching mechanism for a really obfuscated automaton. Maybe even could implement a Turing machine with module_eval writing "tape bits" as the presence or absence of "foo = 'bar' if true==false" assignments. Weird.
  7. Phil Says:
    rickbradley: I was trying to decide between "if true == false" and "if 2 + 2 == 5". For now the law of non-contradiction beats Orwell.
  8. Peter Says:
    Kyle: You know, I hadn't thought of that. The only thing I don't like about that syntax is that you have to put commas after each statement, which breaks the magic a bit. Unless of course there's a way to define a => method, but that doesn't seem to be the case. Thanks for the idea

Leave a Reply

I am a human (check this)

Remember: escape your underscores \_ and indent code at least 4 spaces or incur the wrath of smartypants.