Clutch

How to define constants in Ruby?

How to define constants in Ruby?
Average rating: 0
(0 votes)

Syndicode is still looking for Ruby on Rails developer and continues the series of useful articles about Ruby and Ruby on Rails tricks, hacks, performance etc. You can be a superb professional, but to upgrade your knowledge you have to read more non-stop. So even if you think you know everything, maybe you’ll find something new in our article about how to define constants in Ruby. By this, you will benefit and upgrade your skills! 

One day Michael Hoffman, the author of the original article, was doing a refactor of a controller. He has noticed that story_boolean method always returns a static value, so Michael wondered if it should be the constant instead of a method. And, surprisingly, the code that was calling the constant couldn’t find it even though it was able to find a method in the same context.

The method he changed into a constant is defined in one class but is called from within its superclass. An example that distills the structure of the code:

class MyClass
  def foo_via_method
    foo_method
  end

  def foo_via_constant
    FOO_CONSTANT
  end
end

class SubClass < MyClass
  FOO_CONSTANT = "foo"

  def foo_method
    FOO_CONSTANT
  end
end

The original code was analogous to calling SubClass.new.foo_via_method. After refactoring, the new code was analogous to calling SubClass.new.foo_via_constant.

sub_class_instance = SubClass.new
### THIS WORKS ###
sub_class_instance.foo_via_method
# => "foo"

### THIS DOESN'T ###
sub_class_instance.foo_via_constant
# NameError: uninitialized constant MyClass::FOO_CONSTANT

The version that refers to a method in the subclass returns the desired value, but the version that refers to a constant in subclass throws an error.

Why does the version that uses a method work but the version that uses the constant fail?

Method lookup

How Ruby evaluates that method call? Look for a definition of #foo_via_method in SubClass‘s superclass, MyClass. Ruby looks in the receiver’s class for the method definition, and if it can’t find it, Ruby iterates up the superclass chain (also known as the “ancestor chain”) until it finds a class that implements the method. If it gets all the way to BasicObject and strikes out, it invokes #method_missing.

Why don’t constants behave the same way?

Constant lookup

As with methods, Ruby is also able to look through the superclass chain to find constants.

Ruby is able to resolve OtherSubClass::NAME even though that constant is not defined in OtherSubClass, but rather in its superclass, MyOtherClass. This is the same lookup behavior that caused #foo_via_method to succeed.

So why does #foo_via_constant fail?

The point is: while both lookup algorithms look for definitions in the ancestors of the current class, they differ in how they determine which class counts as “the current class”. For method lookup the current class (the first class in the ancestor chain Ruby will traverse in search of a definition) is the receiver’s class. When we call #foo_via_method on sub_class_instance, the value of self within the body of this method is our receiver, sub_class_instance. So the current class for the purposes of looking up #foo_method is SubClass.

Constant lookup determines the “current class” differently. Rather than relying on the receiver to determine the current class, constant lookup starts with the class containing the method. To be more precise, constant lookup begins its superclass chain search using the class containing the current lexical scope. If no class is open in the current scope, Ruby starts with the Object class.

The context defined by where you are in the code allows local variables to be defined within a block without affecting variables outside of the block, for example. So, while method lookup is relative to the receiver on which the method was called, constant lookup is relative only to the place in the code where Ruby encounters the constant.

What Ruby does when we call sub_class_instance.foo_via_constant?

When we access a constant, the Ruby virtual machine, YARV, calls the getconstant instruction. getconstant calls vm_get_ev_const, a friendly 75-line function that actually implements constant lookup. A local variable holds the root of the lexical scope chain, which represents the place in the code at which the constant was encountered.

Next, the long while block iterates up the lexical scope chain, checking at each step along the way to see if the constant is defined in that context. The line cref = CREF_NEXT(cref) is where we take a step up the chain. The routine keeps climbing the chain until it finds a scope in which the constant is defined or until finally there is no next cref, in which case we exit the whileblock. It’s the latter that will occur in our puzzle when we call SubClass.new.foo_via_constant; The constant FOO_CONSTANT is not defined in the root lexical scope, that within MyClass, and so lexical scope search will come up empty.

But YARV doesn’t stop its search there, it will attempt to resolve the constant by looking through the superclass hierarchy. At first, YARV is aimed to identify the class it will use as the root of this hierarchy. How it determines that class is exactly what we were hoping to learn.

The root lexical scope is within the context of a class (MyClass), so the condition

root_cref && !NIL_P(CREF_CLASS(root_cref))

is satisfied, and klass is initialized as follows:

klass = vm_get_iclass(th->cfp, CREF_CLASS(root_cref));

The function vm_get_iclass just returns the class it’s passed as a second argument (see definition here), so klass gets assigned MyClassis_definedwas passed into vm_get_ev_const as 0, which is falsey, so the return value for the constant lookup we care about will be rb_const_get(klass, id) where klass is MyClass.

rb_const_search function looks through the superclass hierarchy starting with the class it gets passed—in our case, MyClass.

When we call sub_class_instance.foo_via_constant, Ruby searches for FOO_CONSTANT in MyClass and its superclasses. It never looks in SubClass, and so it can’t find a definition for the constant.

So this is the way Ruby’s constant lookup algorithm works:

  1. Check if the constant is defined in the current lexical scope (this is the context defined by where you are in the code)
  2. If not, move up the lexical scope hierarchy and go back to (1)
  3. If you run out of scopes and still haven’t resolved the constant, move on
  4. Check if the constant is defined in the class that’s open in the current lexical scope.
  5. If not, move op the superclass hierarchy and go back to (4).
  6. If you still strike out again: Error!

For methods, the search starts with the receiver’s class. For constants, the search starts with the class you are in at the code location where the constant is called, also known as the lexical scope. That makes a difference.

In the original article we’ve mentioned above you can find why Ruby works this way.

Additional reading will make you more proficient – Ruby Under a Microscope book.

You can find more interesting information by subscribing to our weekly newsletter.

Rate this article, if you like it

Thanks! You’ve rated this material!

Got a project? Let's discuss it!

*By submitting this form you agree with our Privacy Policy.

Mailing & Legal Address

Syndicode Inc. 340 S Lemon Ave #3299, Walnut CA, 91789, USA

Visiting & Headquarters address
Kyiv Sofiivska 1/2a, 01001, Kyiv, Ukraine
Dnipro Hlinky 2, of. 1003, 49000, Dnipro, Ukraine
Email info@syndicode.com
Phone (+1) 9035021111