Skip to Content
Course content

91: const_missing and Dynamic Constants

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Imagine you're staying at a luxury hotel. You walk up to the concierge and ask for a specific local specialist—say, a vintage watch expert. The concierge doesn't have a watch expert standing right there in the lobby, but instead of simply saying, "Sorry, we don't have one," the concierge says, "One moment," disappears into the back, makes a few phone calls, finds a specialist, and brings them to you.

As far as you're concerned, the hotel "had" the expert. You didn't care that the expert wasn't physically present when you asked; you just cared that the request was fulfilled. In Ruby, const_missing is that concierge. It's a hook that lets you intercept the moment Ruby realizes a constant is missing, giving you a chance to find it, create it, or fake it before the program crashes with a NameError.

Trapping the NameError

Normally, when you reference a constant that hasn't been defined, Ruby gives up immediately. But if you define a method called const_missing inside your class or module, Ruby will call that method first. It passes the name of the missing constant as a symbol, which you can then use to decide what to do.

I've seen this used in some really clever ways, but the most common professional use case is "lazy loading" or creating dynamic proxies. Let's look at a basic implementation so you can see the mechanics:

module MagicBox
  def self.const_missing(name)
    puts "Whoops! #{name} wasn't here, but I'll make it for you."
    # We define the constant on the fly so it's there next time
    const_set(name, "I am the value for #{name}")
  end
end

puts MagicBox::Greeting # First time: calls const_missing
puts MagicBox::Greeting # Second time: already defined, no const_missing

Building a Dynamic Configuration Loader

To give you a real-world scenario, let's say you're building a system that loads different configuration sets from a folder. Instead of manually requiring every single config file at the start of your app (which slows down boot time), you can use const_missing to load them only when they are actually referenced.

In this example, if you call AppConfig::Database, Ruby will look for a file named database.rb and load it into the module. This is essentially how many "autoloading" mechanisms in older Ruby frameworks used to work.

module AppConfig
  def self.const_missing(name)
    file_name = name.to_s.downcase
    path = "config/#{file_name}.rb"

    if File.exist?(path)
      require_relative path
      # After requiring the file, the constant should now exist.
      # We return the constant so the original call succeeds.
      const_get(name)
    else
      # If we can't find the file, we should let the NameError happen
      super
    end
  end
end

I should warn you: be very careful with const_set inside const_missing. If you have a typo in your code and you're automatically creating constants for everything that's missing, you'll never see a NameError. You'll just end up with a bunch of "ghost" constants in your system, and your bugs will become incredibly hard to track down. Always provide a fallback—like calling super—if the dynamic lookup fails.

The Danger of Infinite Loops

One last thing to watch out for. If you call a constant inside your const_missing method that is also missing, you'll trigger const_missing again. If you aren't careful, you'll hit a SystemStackError (stack overflow) very quickly. Always ensure that the logic inside your hook is "safe" and doesn't accidentally trigger another missing constant lookup in a loop.




📋 Practical Task

The Automatic API Endpoint Generator

Your task is to build a module called API that acts as a gateway to various service endpoints. Instead of defining every single endpoint (like Users, Orders, Products) manually, you will use const_missing to generate a "Handler" object on the fly.

Requirements:

  • Create a module named API.
  • Implement self.const_missing(name).
  • Inside the hook, create a simple Handler class (or use a Struct/OpenStruct) that has a method called call.
  • The call method should return a string: "Calling endpoint: [constant_name]".
  • The API module should use const_set to store the handler so that subsequent calls to the same endpoint don't trigger the hook again.
  • If the constant name starts with an underscore (e.g., API::_Secret), the hook should call super to trigger a standard NameError.

Test your code with:

puts API::Users.call    # Should print: Calling endpoint: Users
puts API::Orders.call    # Should print: Calling endpoint: Orders
puts API::Users.call    # Should NOT trigger const_missing this time
API::_Secret.call        # Should raise NameError
Rating
0 0

There are no comments for now.

to be the first to leave a comment.