Natural, not simple. (Photo by pandu ior)

A Puzzle About Ruby Constants

Ruby’s algorithm for finding the definition of a constant is more complex than you might think.

class StoryBooleansController < BaseController
def create
update_story(story_boolean => true)
end

def destroy
update_story(story_boolean => false)
end

private

def update_story(attributes)
story = Story.find(params[:story_id])
story.update!(attributes)
end
end
class StarsController < StoryBooleansController
private

def story_boolean
:starred
end
end
class StoryBooleansController < BaseController
def create
update_story(STORY_BOOLEAN => true)
end

def destroy
update_story(STORY_BOOLEAN => false)
end

# [SNIP]
end
class StarsController < StoryBooleansController
STORY_BOOLEAN = :starred
end

The Puzzle, Distilled

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
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

Method Lookup

Constant Lookup

class MyOtherClass
NAME = "Michael"
end

class OtherSubClass < MyOtherClass
end

OtherSubClass::NAME
# => "Michael"

Going to the Source

Photo by Christian Bisbo Johnsen

Get constant variable id. If klass is Qnil, constants are searched in the current scope. If klass is Qfalse, constants are searched as top level constants. Otherwise, get constant under klass class or module.

/*********************************************
From vm_insnhelper.c in definition of `vm_get_ev_const`
*********************************************/
/* search self */
if (root_cref && !NIL_P(CREF_CLASS(root_cref))) {
klass = vm_get_iclass(th->cfp, CREF_CLASS(root_cref));
}
else {
klass = CLASS_OF(th->cfp->self);
}

if (is_defined) {
return rb_const_defined(klass, id);
}
else {
return rb_const_get(klass, id);
}
root_cref && !NIL_P(CREF_CLASS(root_cref))
klass = vm_get_iclass(th->cfp, CREF_CLASS(root_cref));

Summing Up: Method vs. Constant Lookup

Why does Ruby work this way?

Ruby is simple in appearance, but is very complex inside…

–Matz

And what about that controller?

class StoryBooleansController < BaseController
def create
update_story(STORY_BOOLEAN => true)
end

def destroy
update_story(STORY_BOOLEAN => false)
end

private

def update_story(attributes)
story = Story.find(params[:story_id])
story.update!(attributes)
end
end
class StarsController < StoryBooleansController
STORY_BOOLEAN = :starred
end
class StoryBooleansController < BaseController
def create
update_story(self.class::STORY_BOOLEAN => true)
end

def destroy
update_story(self.class::STORY_BOOLEAN => false)
end

# [SNIP]
end

Thanks!

Photo by Stefan Steinbauer

--

--

Dad. Once and future software engineer.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store