Dynamic method dispatch with Kernel#send
One of the fundamental ideas in Ruby is that our programs are made up of objects that send and receive messages from each other. Thus the deeper mental model of what we do when we execute object.some_method is that we are "sending the some_method message to object".
One way this becomes explicit is with Kernel#send, available on all objects, which allows us to send a message by passing a name and arguments determined at runtime:
class MyClass
def greeting(name)
"Hello, #{name}!"
end
end
instance = MyClass.new
instance.send(:greeting, "Alice")
# "Hello, Alice!"
Let's make a version of the CLI tool we created yesterday while discussing ARGV that dispatches to a module method rather than a lambda:
puts "Usage: cli.rb <command> [args...]" and exit if ARGV.empty?
module CLI
def self.greet(name)
puts "Hello, #{name}!"
end
def self.add(x, y)
puts x.to_i + y.to_i
end
end
command = ARGV.shift.to_sym
abort "ERROR: Unknown command - #{command}" unless CLI.singleton_methods.include?(command)
CLI.send(command, *ARGV)
So effortless!
Note how we use Object#singleton_methods to verify that the command is defined on the module before sending it.
Of course there is nothing stopping us from using a class instead of a module, but following the principle of functions are simpler than classes and noting that we don't need to maintain any state, a module is a more natural fit here.
History
Kernel#send has been around since Ruby's inception.
The idea of objects sending messages to each other comes from Smalltalk, though Smalltalk's equivalent of send is called perform.
Another language which leans heavily into the message sending metaphor is Objective-C, where all method calls are actually message sends, and the equivalent of send is called objc_msgSend.