05 September 2012

Don't forget about to_s and to_param!

If you’re a Rails developer, there’s one method you’re using hundreds of times a day, most of the time implicitly, and that method is #to_s. This method returns a string representation of an object and it gets called whenever you do string interpolation either as "#{object}" or <%= object %>. Straight out of the box it’s pretty useless as it doesn’t return anything of any use. But it can be overridden as a class method to return something that is useful. For example, I have a User class:

class User < ActiveRecord::Base
    attr_accessible :firstname, :lastname, :username
end

now in my view whenever I want to display the user’s full name I have to do:

<%= "#{user.firstname} #{user.lastname}" %>

I can make this more expressive by just defining the

#to_s method  in my User class.

class User < ActiveRecord::Base
    attr_accessible :firstname, :lastname, :username

    def to_s
        "#{firstname} #{lastname}"
    end
end

Now in my view I can just ask for the user object like so:

<%= user %>

and as it’s output,

#to_s will be called on it giving us the firstname and lastname as a string. Another method that can be overridden for convenience is #to_param. This gets called whenever a named route helper method is called (for example user_path(user) ). #to_param is being called on user which by default returns its ID. We can override this behaviour to create nicer permalinks to a resource or use a different parameter all together to find a resource. I’ll demonstrate both below. Using the same User model I’ll define a #to_param method to act as our permalink:

class User < ActiveRecord::Base
    attr_accessible :firstname, :lastname, :username

    def to_param
        "#{id}-#{username}"
    end
end

Now whenever we use the User object in a named route helper it will generate a link like so: “http://example.com/user/6-nitza”. The beauty of doing things this way is that it doesn’t break anything. When we try to retrieve the User in the controller using

    User.find(params[:id])

The #find method will convert its input to an integer thus leaving behind a 6, the ID of the user. If however we wanted to change this behaviour, and start generating urls with just the username and not the ID, we could just return the username in the

#to_param method like so:

class User < ActiveRecord::Base
    attr_accessible :firstname, :lastname, :username

    def to_param
        username
    end
end

This means, however, that any calls to

#find on the User object will need to become #find_by_username instead.