views:

534

answers:

4

I have an author class:

class Author < ActiveRecord::Base
  def to_s
    name
  end
end

Defining to_s allows me to do puts Author.first, but not puts Author.first.rjust(10):

NoMethodError: undefined method `rjust' for #<Author:0x21eb5d4>

Wouldn't it be better if Ruby automatically tried to_s before the string method in cases like this? Is there any way to get this behavior?

+15  A: 

First off, no, it wouldn't. I don't want ruby to just say "Hey, maybe this is a string method, let me see if I can run it after running to_s" on an arbitrary object. That being said, there are two solutions to what you want to do:

If you want to say "On any Author instance, if someone calls a method that it doesn't have, but that String does, then it should magically call to_s", then do this:

class Author < ActiveRecord::Base
  def to_s
    name
  end

  def method_missing(s, *a)
    x = to_s
    if x.respond_to? s then
      return x.send(s, *a)
    else
      super
    end
  end
end

If you want to say "rjust on anything that isn't a String should mean calling to_s first", then:

class Object
  def rjust(*a)
    to_s.rjust(*a)
  end
end

(Repeat with other methods as desired; note that this allows you to do things like 86.rjust(10); whether that's a good thing or not may be a matter of taste)

Daniel Martin
That `super.method_missing(s, *a)` is incorrect. It should just be `super`, which calls the superclass's version of the method with the same arguments. In Ruby, super acts like a method rather than a receiver.
Chuck
@Chuck: Thanks, fixed. I did test my code before posting, but was fooled because the action of "super" is to throw an exception - this means that for this particular case, the incorrect syntax worked as expected. (Since ruby never tries to call "method_missing" on the return value of super)
Daniel Martin
A: 

*Wouldn't it be better if Ruby automatically tried to_s before the string method in cases like this?*

You're heading down a slippery slope by asking for a language to "just do what I mean (most of the time)." While it might make sense in many cases, it's bound to foul things up around the edges. In your case, who's to say that rjust isn't a method defined on Author (or one of its superclasses).

Dave W. Smith
+1  A: 

Actually... there IS a method that would do something vaguely similar to this: to_str

However, it still wouldn't be called implicitly for this particular case. The existence of a to_str method in Ruby is effectively equivalent to saying, "Any method that would normally take a String as a parameter may implicitly convert this object to a String by calling the to_str method." Most methods in the standard library will attempt to use this technique to coerce to String, and a lot of 3rd party libraries do as well.

In the example you gave, however, it would be absolutely inappropriate for Ruby to detect that an unhandled message was String-like and convert. This would lead to all kinds of errors, bugs, and general misbehavior in a lot of non-String related code, especially any of the code out there like Builder that relies on the normal missing method behavior.

Bob Aman
+1  A: 

Check out ruby's support for delegation and method forwarding

Martin DeMello