I first learned about methodmissing when <a href="http://devlicio.us/blogs/christopherbennage/">Christopher Bennage asked me about it. Even after looking it up and explaining it to Christopher, I didn't find any practical use for it in the things I was doing.

At least until tonight, that is.

I've been working on expanding Hair Forecast into Canada. I have the Canadian weather data described in active record like so:

    createtable :canadaforecasts do |t|
      t.column :fcstdate, :date
      t.column :lat, :float
      t.column :lon, :float
    end
<!--break-->
    createtable :canadaparameters do |t|
      t.column :canadaforecast_id, :integer
      t.column :parameter, :string
      t.column :interval, :int
      t.column :value, :float
    end

As you can imagine from that, a canadaforecast has_many

canadaparameters. Each parameter is one of T for temperature, D for dewpoint, W for wind, etc. Each parameter has an interval, which is described in hours past 0:00 UTC. This is so I can have each weather parameter for morning, day, night, etc.

Now here's the interesting part. When I'm retrieving a forecast, and wish to access temperature for the 12th hour of the period, I would end up with this:

forecast = Canadaforecast.find(somecriteria)
forecast.canadaparameters.findbyparameterand_interval("T", 12)
That second line just ain't that pretty. Ruby is supposed to be pretty. So I decided to simplify it.

I could create a fetch method in the Canadaforecast model that simply takes two args (parameter and interval) and returns the value. So then I'd have a bunch of lines like:

t = forecast.fetch("T", 12)
d = forecast.fetch("D", 12)
But what if I thought the following was more readable and just plain looked cool?
t = forecast.T(12)
d = forecast.D(12)
Well, methodmissing is the tool for this. Consider the following model in my Rails app:
class Canadaforecast < ActiveRecord::Base
  hasmany :canadaparameters

Valid_parameters = [ "T", "D", "C", "W", "P", "H" ]

def methodmissing(method, *args) if Validparameters.include?(method.tos) fetch(method.tos, args[0]) else super end end

private def fetch(parametername, interval) parameter = self.canadaparameters.findbyparameterandinterval(parametername, interval) if parameter.nil? nil else parameter.value end end end

The methodmissing method checks to see if you are trying one of the valid forecast parameters as a method. If not it just sends you back to the super and throws an error. If you have used a valid parameter as a method, then it calls the simple fetch method and passes in the parameter and interval you are trying to get. Note how methodmissing provides an array of args.

Now I can fetch any forecast parameter for any interval with very easy to read code.

No model would be complete without a proper test, so just to show off my (lack of) testing skills, here's the test for the model above:

require File.dirname(FILE) + '/../test_helper'

class CanadaForecastTest < Test::Unit::TestCase fixtures :canadaforecasts, :canadaparameters

def testfetchingparametersbyusingparameternameasmethod forecast = Canadaforecast.find(1) assertnotnil forecast
value = forecast.H(12) assert_equal 10, value end

def testfetchingparametersnotfound forecast = Canadaforecast.find(1) assertnotnil forecast
value = forecast.H(1) assert_nil value end end


Published

2008-07-08

Categories


Tags