Search code examples
ruby-on-railsrubyinheritanceconstantsautoload

When do you need to prefix Ruby constants with "::"? (uninitialized constant error)


The error is something like NameError: uninitialized constant Foo::Bar (when Bar is not a descendant of Foo).

I get that it has something to do with loading (autoloading?) constants, and I feel that anything inside of lib/ is safe to not prefix with :: because it's autoloaded (or something).

An example that just happened to me is something like this:

app/assets/classes/base_class.rb

class BaseClass
end

app/assets/some_module/some_class.rb

module SomeModule
  class SomeClass < BaseClass
  end
end

I was running a spec and got "An error occurred while loading [file]": NameError: uninitialized constant SomeModule::SomeClass::BaseClass.

Now, I get that it's trying to look for BaseClass within SomeModule::SomeClass. But this was working a few hours before and then stopped after no changes to these files.

So, I could just tack on a :: and go with class SomeClass < ::BaseClass, but without understanding why it feels bad and then I'm like, do I need to pepper all my code with :: all the time?


Solution

  • When do you need to prefix Ruby constants with "::"?

    When you refer to a constant Ruby will look for it in the current module nesting.

    module SomeModule
      puts Module.nesting.inspect # the current module nesting is [SomeModule] 
    end
    

    The module nesting is primarily set by using the module and class keywords to open a class/module.

    If its not found there it will keep going upwards in the module nesting until it reaches main and if the constant still isn't found by then you'll get a missing constant error.

    This error can be somewhat confusing since the message contains the module nesting where it started looking for the constant.

    By prefixing a constant with the scope resolution operator (::) you're explicitly telling Ruby to resolve the constant from the top level namespace:

    module Bar
      def self.hello
        puts "Hello from the top level Bar"
      end
    end
    
    module Foo
      module Bar
    
    
        def self.hello
          puts "Hello from Foo::Bar"
        end
      end
    
      def self.test
         Bar.hello # the current module nesting is Foo.
         ::Bar.hello
      end
    end 
    
    Foo.test
    # Will output:
    # Hello from Foo::Bar
    # Hello from the top level Bar
    

    In most cases its not strictly necissary but it is a good practice though to explicitly refer to constants outside your own namespace (dependencies).

    So why did my code stop working?

    Without an actual reproducable example its near impossible to tell. It can either be "programmer related issues" (you screwed up and moved/deleted/renamed the file) or it can be tied to issues with the autoloader in use. The classic autoloader was far more prone to buggy behavior due to the way it monkeypatched Object#constant_missing.