Dynamic dispatch via Module#const_get
Ruby's Module#const_get method allows us to retrieve a constant by name at runtime:
module MyModule
GREETING = "Hello, world!"
class MyClass
end
end
MyModule.const_get("GREETING")
# "Hello, world!"
MyModule.const_get("MyClass")
# MyModule::MyClass
Let's recreate the example we introduced two days ago in a way that dispatches via const_get:
help = ARGV.delete("--help")
puts "Usage: cli.rb <command> [args...]" and exit if ARGV.empty?
module Commands
module Greet
extend self
def description = "Greet someone"
def usage = "cli.rb greet "
def call(name) = puts "Hello, #{name}!"
end
module Add
extend self
def description = "Add two numbers"
def usage = "cli.rb add "
def call(x, y) = puts x.to_i + y.to_i
end
end
command_name = ARGV.shift.capitalize.to_sym
unless Commands.constants.include?(command_name)
abort "ERROR: Unknown command - #{command_name.downcase}"
end
command = Commands.const_get(command_name)
if help
puts "Command: #{command_name.downcase}"
puts command.description
puts "Usage: #{command.usage}"
exit
end
command.call(*ARGV)
With these simple commands, the step up in verbosity from our previous examples may not be worth it, but if our commands were complex enough to require multiple methods, this approach could provide a better organisational structure.
Note the usage of Module#constants to verify that the command is defined on the module before calling const_get. This is important to avoid unsafe access to constants that may not be intended as commands:
Commands.constants
# [:Greet, :Add]
# But const_get doesn't enforce namespace locality by default
Commands.const_get("Kernel")
# Kernel
# Passing false as a second argument prevents searching parent namespaces
Commands.const_get("Kernel", false)
# NameError: uninitialized constant Commands::Kernel
# However, we can bypass this by providing an absolute path with ::
Commands.const_get("::Kernel", false)
# Kernel
Enjoy using const_get but remember to do so responsibly!
History
Module#const_get and Module#constants shipped in Ruby 1.2 in 1998.
In Ruby 1.9, released in 2007, both methods gained the optional inheritance flag demonstrated above, allowing calls like const_get(:Kernel, false) and constants(false) to avoid searching ancestor namespaces. Around the same time, Module#constants changed from returning strings to returning symbols.
Ruby 2.0, released in 2013, expanded const_get to accept qualified constant paths such as "Foo::Bar::Baz". That made the method more powerful, but also increased the need to validate user-provided input for dynamic dispatch.