55 Minutes

Welcome to the 55 Minutes blog.
55 Minutes is a web development consultancy in San Francisco building apps for design-led businesses and startups. On this blog we discuss the technology tools of our trade: Ruby on Rails, Django, Sass, OS X, and more.

Trivia: Constants in Ruby 1.9.2 vs 1.9.3

Ruby 1.9.3 introduced a subtle and unexpected change in how constants are resolved. This one is tricky!

Consider the following Connection class, defined using the Struct.new helper.

# Define a simple class with a DEBUG constant
#
Connection = Struct.new(:name, :state) do
  DEBUG = false
  def self.debug?
    DEBUG
  end
end

# A quick sanity check
Connection.debug?
=> false
connection.rb – View Gist

Straightforward, right? Here’s where things get interesting.

# Let's now change the constant from false to true
Connection::DEBUG = true

# ruby 1.9.2-p180
Connection.debug?
=> true

# ruby 1.9.3-p0
Connection.debug?
=> false
connection1-riddle.rb – View Gist

What just happened?

The answer to this riddle has to do with Struct.new. As it turns out, calling Struct.new and passing a block does not create a new scope as far as constants are concerned. In other words, while the indentation implies that DEBUG is contained within the scope of Connection, in truth DEBUG gets registered as a global constant.

So as far as Ruby is concerned, what we actually declared was this:

DEBUG = false
Connection = Struct.new(:name, :state) do
  def self.debug?
    DEBUG
  end
end
connection2-actual.rb – View Gist

Later when we write Connection::DEBUG = true, what do we really mean? This is where Ruby 1.9.2 and 1.9.3 diverge.

In Ruby 1.9.2, the interpreter assumes you meant to write DEBUG = true and changes the global constant. This behavior is arguably a bug.

Ruby 1.9.3 “corrects” this behavior. It interprets Connection::DEBUG = true to mean “create a brand new constant named Connection::DEBUG and assign it to true”. And so the global DEBUG remains unchanged.

Now the results make sense:

# ruby 1.9.2-p180
Connection::DEBUG = true # Actually does DEBUG=true
Connection.debug?        # Reads new value of DEBUG
=> true

# ruby 1.9.3-p0
Connection::DEBUG = true # Creates new constant Connection::DEBUG
Connection.debug?        # Reads unchanged value of DEBUG
=> false
connection3-explained.rb – View Gist

Now that we know what’s going on, how would we change the code to make it less ambiguous?

One option is to expicitly declare the scope of the constant, like this:

Connection = Struct.new(:name, :state) do
  def self.debug?
    Connection::DEBUG
  end
end
Connection::DEBUG = false
connection4-alt.rb – View Gist

The best approach is to probably drop the Struct.new fanciness and just use a simple class:

class Connection
  DEBUG = false
  
  attr_accessor :name
  attr_accessor :state
  
  def init(name, state)
    @name = name
    @state = state
  end
  
  def self.debug?
    DEBUG
  end
end
connection5-alt.rb – View Gist

Have something else to add? Contribute your ideas to the Gist for this post at GitHub.

comments powered by Disqus