Writing Conversion Methods in Rails
As developers, we're occasionally tasked with maintaining software that we weren't directly involved in crafting. We don't have extensive knowledge of the domain, yet we can read the code, (hopefully) understand what's there, and modify it. We'll even attempt to improve the application and leave it in a better state than we found it. What follows is one such tale.
I recently had to make a change on a legacy Rails application. In order to make the change, I had to find the correct instance of
Site. I didn't know which attribute on
Site to query for, so the first thing I did was open the database schema to see a list of all attributes on
Site. Wouldn't it be nice if I didn't need to look at the database schema to find the attribute I need? What if I told you I had an easier way of finding instances of your classes?
Avdi Grimm reminded me in his book Confident Ruby (which we loved) about Ruby's conversion methods. Specifically, methods like
Array('thing') # => ["thing"] and
URI('http://place.com') # => URI object with the URL of 'http://place.com'. I thought, wouldn't it be great if I could write
Site('widget') and have it return the correct instance of
Site? So that's just what I did.
Writing the conversion method was fairly simple:
def Site(site) if site.is_a?(Site) site elsif site.is_a?(String) Site.find_by_subdirectory!(site) else raise ArgumentError, 'bad argument (expected Site object or string)' end end
The above code says that if
site is already an instance of
Site, then return it. If
site is a string, find the instance of
site is neither an instance of
Site nor an instance of
String, then raise an
ArgumentError, informing the developer what the method was expecting. Great -- now I have a conversion method in which I can write
Site('widget') and get the instance of the Widget site. But where do I put this method in a Rails application?
I could put it in an initializer. But it feels like it should really go with the
Site class definition, in case a developer wants to extend it in the future. Where does Ruby place its conversion methods? To answer that question, I took a peek at Ruby's URI class. I noticed how the conversion method was with the class definition and at the bottom of the file, within the
Kernel module. I took this precedence and modified the
Site class to the following:
class Site < ActiveRecord::Base # … wow. such code … end module Kernel # Returns +site+ converted to a Site object. def Site(site) if site.is_a?(Site) site elsif site.is_a?(String) Site.find_by_subdirectory!(site) else raise ArgumentError, 'bad argument (expected Site object or string)' end end end
I really like that I can now retrieve a site with
Site('widget') and not have to look up the correct attribute to query by.
What do you think? Do you use conversion methods in your Rails applications? Let me know in the comments below.