Testing Whether an Object Is String-Like
Problem
You want to see whether you can treat an object as a string.
Solution
Check whether the object defines the to_str method.
'A string'.respond_to? :to_str # => true Exception.new.respond_to? :to_str # => true 4.respond_to? :to_str # => false
More generally, check whether the object defines the specific method of String you're thinking about calling. If the object defines that method, the right thing to do is usually to go ahead and call the method. This will make your code work in more places:
def join_to_successor(s) raise ArgumentError, 'No successor method!' unless s.respond_to? :succ return "#{s}#{s.succ}" end join_to_successor('a') # => "ab" join_to_successor(4) # => "45" join_to_successor(4.01) # ArgumentError: No successor method!
If I'd checked s.is_a? String instead of s.respond_to? :succ, then I wouldn't have been able to call join_to_successor on an integer.
Discussion
This is the simplest example of Ruby's philosophy of "duck typing:" if an object quacks like a duck (or acts like a string), just go ahead and treat it as a duck (or a string). Whenever possible, you should treat objects according to the methods they define rather than the classes from which they inherit or the modules they include.
Calling obj.is_a? String will tell you whether an object derives from the String class, but it will overlook objects that, though intended to be used as strings, don't inherit from String.
Exceptions, for instance, are essentially strings that have extra information associated with them. But they don't subclass class name "String". Code that uses is_a? String to check for stringness will overlook the essential stringness of Exceptions. Many add-on Ruby modules define other classes that can act as strings: code that calls is_a? String will break when given an instance of one of those classes.
The idea to take to heart here is the general rule of duck typing: to see whether provided data implements a certain method, use respond_to? instead of checking the class. This lets a future user (possibly yourself!) create new classes that offer the same capability, without being tied down to the preexisting class structure. All you have to do is make the method names match up.
See Also
- Chapter 8, especially the chapter introduction and Recipe 8.3, "Checking Class or Module Membership"