Alternative to method_missing in Rails

Yes. it has been a long time since I’ve posted anything, but I’m sure I’ll want to refer to this in the future.

While developing some new functionality for TheBigFork, I found an opportunity to take advantage of the mysterious “method_missing” method in my ActiveRecord models. I have two models: Website and Setting. A website has many settings. Nothing earth-shattering there. To simplify the explanation, let’s just say I want to be able to load a value from the settings table with something like this:

website.site_name
=> "TheBigFork"

Where the “site_name” comes from the related settings. The longer, built-in way to do this is:

website.settings.where(:name => 'site_name').first.value
=> "TheBigFork"

Okay, so the “method_missing” approach would look something like this:

class Website < ActiveRecord::Base
  has_many :settings

  def method_missing(method)
    self.settings.where(:name => method).first.value
  end
end

(I really need to figure out a better way to post code in WordPress. Maybe it is time to move this blog to my own server where I can manage plugins. Anyway…)

That approach sort-of works. It seems to work better in some versions of rails than others. Some problems show up because “website.respond_to?” returns false for these pseudo methods. That could be fixed by adding this to the Website class:

def respond_to?(method)
  if self.method.to_s != 'settings' && self.settings.where(:name => method)
    true
  else
    super
  end
end

But that still gave me all sorts of “stack level too deep” errors. I stumbled upon Jay Field’s post, Replace method_missing with dynamic method definitions. Just what I needed. Except it wasn’t a Rails-specific solution. Just ruby in general. Apparently, classes that inherit from ActiveRecord::Base may or may not load with your own initialize method. So, I used the after_initialize hook like so:

class Website < ActiveRecord::Base
  has_many :settings
  after_initialize :dynamic_methods

  def dynamic_methods
    self.settings.each do |setting|
      (class << self; self; end).class_eval do
        define_method setting.name.to_sym do |*args|
          self.__send__("get_value", setting.name, *args)
        end
      end
    end
  end

  def get_value(name)
    self.settings.where(:name => name).first.value
  end

end

Works like a charm. It even answers correctly to the “respond_to?” for each setting.

Now, it is a bit expensive to dynamically create lots of methods every time you initialize a new instance, so I probably wouldn’t recommend this approach if you have tons of settings for each website. However, in my application, I actually have a third model involved (Brand) which shares settings among websites. The website actually caches the values, and this solution works nicely.

First Time Visitors

Seth Godin posted an idea about first time customers to different businesses. The idea is to have sign with information for first timers. It is one of those ideas that makes me think, “Duh, why didn’t I think of that.”

Anyway, I’m mostly writing this post as a note to myself to remember to do this on my online venture. Right now, of course, I’m thinking it would make a nice addition to the homepage for TheBigFork. Perhaps we can get rid of the big block of text on the home page and replace it with a simple link “First Time Here?” When clicked, it could show the first timer text inline.

Cooking vs. Coding

Jason at 37Signals wrote about giving away your cookbook. This intrigued me for a number of reasons.

I noticed a correlation between geeks and high-quality food over a year ago. This post reinforces the notion that today’s computer geeks appreciate good cooking. (Otherwise, why would Jason and the responders even care to make the analogy?) Many will try their hand at it, too. I sure enjoy it. I have all but one of Bobby Flay’s books and love finding new ways to combine interesting flavors to create powerful new food experiences.

Programming has similarities to cooking. You start with basic building blocks and create something new for others to enjoy (or just for yourself). It is an art, and both seem to take the same kind of thinking to do it well.

In designing TheBigFork, Sam and I talked about Open Source Chefs. It is interesting that somebody else noticed the same thing. Someday we might expand the site with an Open Source Chef arena. For now, though, I think it is best to keep it simple–just like it says in 37Signal’s Getting Real. I hope my fellow geeks out there will enjoy TheBigFork and find it useful.

Getting ready to launch TheBigFork

A few months ago, I was planning to have a lunch meeting with my friend, Kenton. We hadn’t decided on a location so I thought I’d try to find a good spot.

Kenton works in Draper. I work in Sandy. In the past, we’ve met up at restaurants just off I-15 at 126th South. Try using the yellow pages or Google to solve the problem. It is a headache. All I wanted was a map of the area with pointers showing where the various restaurants are. Nothing quite works that way.

So, I decided to build it. With help from Sam and Kenton, we should have the site ready for launch this week. Visit www.thebigfork.com to take a look. It isn’t going to create world peace, but I hope others find it useful.

There are 2 concerns I have about it, though.

1. The source of the listings was a little out-dated and incomplete. The site is designed to let users update the core information. I hope users will do it. (The current information is still better than Google local.)

2. Speed. There are almost 500,000 restaurants in the database. There are also tons of tags that have been generated. The database is pretty huge. The site is a little sluggish because of it. I spent time making sure the code is nice and trim, but the database slows it down. I may need to look at getting a beefier server to run mysql.

Other than that, I think the site serves its purpose really well. So, give it a try and let me know what you think.