Dynamically add data accessor methods on “static” Rails data model

An useful metaprogramming spell I recently played with is the Module#define_method(), which dynamically adds an instance method to the class on which is called.


I found it particularly useful to add data accessor methods on “static” Rails data model: suppose I’m working an e-commerce Rails webapp, and I have a Country model which maps the countries suitable for shipping, or a PaymentType model which represents all the possible payment types.

For these kind of models (and tables), which are typically static (they don’t change often), you often have to access specific values, say Country.italy or PaymentType.credit_card.

In these cases, defining dynamically an accessor method may be useful and more clear than always perform a find_by_name("my value").

So, for example, I open up my country.rb model class and add these lines

class << self
  Country.all.each do |each_country|
    define_method(each_country.name.downcase.gsub('.', '').gsub(' ', '_')) do

And then opening the Rails console I will be able to type

1.8.7@epistore > Country.sri_lanka
# {
                :id => 59,
              :zone => "U9",
           :enabled => true,
        :created_at => Tue, 20 Apr 2010 17:01:45 CEST +02:00,
        :updated_at => Tue, 20 Apr 2010 17:01:45 CEST +02:00,
          :iso_code => "LK",
    :country_set_id => nil

Just a note: as I said, Module#define_method() will add an instance method on the class. To add a class method, which is what I want, we have to use a different approach, using the class << self syntax to add a singleton method in the receiver.

I may also add a query method on each Country instance to check that country against another country (for example, I may ask my_country.italy?)

  Country.all.each do |each_country|
    define_method(each_country.name.downcase.gsub('.', '').gsub(' ', '_').concat('?')) do
      has_iso_code? each_country.iso_code

And then, after issuing a reload! command in the Rails console, I may type:

1.8.7@epistore > Country.usa.usa?
1.8.7@epistore > Country.usa.italy?
1.8.7@epistore > Country.usa.south_korea?
1.8.7@epistore > Country.south_korea.south_korea?

Depending on the kind of Rails app you have, these may be a useful tip.