Ruby’s respond_to?
method is a powerful tool for checking whether an object can respond to a particular method call. However, many Ruby developers aren’t aware that this method has different forms that exhibit different behaviors, particularly when it comes to method visibility.
The Basic Form: respond_to?(:method_name)
The most commonly used form of respond_to?
only checks for public methods:
class Person
def initialize(name)
@name = name
end
def greet
"Hello, I'm #{@name}"
end
private
def secret_info
"This is private information about #{@name}"
end
end
person = Person.new("Alice")
# Check for public method
puts person.respond_to?(:greet) # => true
# Check for private method
puts person.respond_to?(:secret_info) # => false
The Extended Form: respond_to?(:method_name, true)
When you pass true
as the second argument, respond_to?
checks for methods of all visibility levels (public, protected, and private):
# Using the same Person class from above
person = Person.new("Alice")
# Check for public method (both forms return true)
puts person.respond_to?(:greet, true) # => true
puts person.respond_to?(:greet) # => true
# Check for private method (only extended form returns true)
puts person.respond_to?(:secret_info, true) # => true
puts person.respond_to?(:secret_info) # => false
A More Comprehensive Example
Let’s look at a more detailed example that demonstrates all visibility levels:
class BankAccount
def initialize(balance)
@balance = balance
end
# Public method
def deposit(amount)
@balance += amount
"Deposited $#{amount}. New balance: $#{@balance}"
end
# Public method
def balance
@balance
end
protected
def transfer_to(other_account, amount)
return "Insufficient funds" if @balance < amount
@balance -= amount
other_account.receive_transfer(amount)
end
def receive_transfer(amount)
@balance += amount
end
private
def validate_transaction(amount)
amount > 0 && amount <= @balance
end
def log_transaction(type, amount)
puts "#{Time.now}: #{type} of $#{amount}"
end
end
account = BankAccount.new(1000)
# Public methods
puts account.respond_to?(:deposit) # => true
puts account.respond_to?(:deposit, true) # => true
puts account.respond_to?(:balance) # => true
puts account.respond_to?(:balance, true) # => true
# Protected methods
puts account.respond_to?(:transfer_to) # => false
puts account.respond_to?(:transfer_to, true) # => true
puts account.respond_to?(:receive_transfer) # => false
puts account.respond_to?(:receive_transfer, true) # => true
# Private methods
puts account.respond_to?(:validate_transaction) # => false
puts account.respond_to?(:validate_transaction, true) # => true
puts account.respond_to?(:log_transaction) # => false
puts account.respond_to?(:log_transaction, true) # => true
Practical Use Cases
1. API Design and Method Delegation
When building APIs or implementing method delegation, you might want to check only for public methods:
class APIWrapper
def initialize(service)
@service = service
end
def method_missing(method_name, *args, &block)
if @service.respond_to?(method_name) # Only public methods
@service.send(method_name, *args, &block)
else
super
end
end
def respond_to_missing?(method_name, include_private = false)
@service.respond_to?(method_name) || super
end
end
2. Debugging and Introspection
When debugging or doing introspection, you might want to see all available methods:
class MethodInspector
def self.analyze_object(obj)
puts "=== Method Analysis for #{obj.class} ==="
public_methods = []
private_methods = []
obj.class.instance_methods(false).each do |method|
if obj.respond_to?(method)
public_methods << method
elsif obj.respond_to?(method, true)
private_methods << method
end
end
puts "Public methods: #{public_methods.sort}"
puts "Private methods: #{private_methods.sort}"
end
end
# Usage
person = Person.new("Bob")
MethodInspector.analyze_object(person)
3. Conditional Method Calls with Privacy Awareness
Sometimes you need to know if a method exists regardless of its visibility:
class ConditionalCaller
def safe_call(obj, method_name, *args)
if obj.respond_to?(method_name, true)
begin
obj.send(method_name, *args)
rescue NoMethodError => e
if obj.respond_to?(method_name)
raise e # Re-raise if it's a public method (unexpected error)
else
"Cannot call private/protected method: #{method_name}"
end
end
else
"Method #{method_name} does not exist"
end
end
end
Key Takeaways
respond_to?(:method)
- Checks only for public methodsrespond_to?(:method, true)
- Checks for methods of all visibility levels- The second parameter is a boolean that controls whether to include private methods
- Use the basic form for API design and public interfaces
- Use the extended form for debugging, introspection, and when you need complete method visibility
Important Notes
- The
include_all
parameter (second argument) defaults tofalse
- This behavior is consistent across all Ruby versions
- When using
send
to call private methods, you still need to be aware of method visibility rules - Consider the principle of encapsulation when deciding which form to use
Understanding these nuances of respond_to?
will help you write more robust and intentional Ruby code, especially when dealing with metaprogramming, API design, and object introspection.