views:

114

answers:

2

I have a class similar to the following:

class FruitKinds < ActiveRecord::Base
  Apple = FruitKinds.find(:all).find { |fk|
    fk.fruit_name == :apple.to_s
  }

  # ... other fruits

  # no methods
end

Apple and other specific Fruits are commonly used as default values elsewhere in my application, so I want a handy means to refer to them in an enumerable, static-ish way.

However, there's a problem. There is a database migration to create the FruitKinds table and populate it with the special Fruits like Apple. When the database migration runs to initialize FruitKinds, rake fails to start because it first loads FruitKinds, which then makes a call to the database, which of course fails since the FruitKinds table is not yet there.

The workaround is to comment out the FruitKinds::* fields while the migration runs, but that is awful and hacky. What's the best way to do this?

A: 

That's a pretty common gotcha, to the point that I now consider any database access at the class definition level an antipattern. The alternative is to make it a lazy property. The simplest way to do this is simply to make it a class-level method instead of a constant. Note that you can have capitalised methods in Ruby, so you can make it look like a constant if you want:

class FruitKinds < ActiveRecord::Base
  def self.Apple 
    @apple ||= FruitKinds.find(:all).find { |fk|
      fk.fruit_name == :apple.to_s
    }
  end

  # ... other fruits

  # no methods
end

Or if you want to get fancy you can use const_missing to dynamically create the constant the first time it is accessed.

As a side note, that's about the most inefficient way possible to find a record by name ;-)

Avdi
Couldn't you write self.Apple such that it hits the db a maximum of once? You could save the value to a field somewhere and have an attr_accessor.
Kyle Kaitan
I was leaving memoization as an exercise to the reader, but there - I've changed the code above to save the value in a class variable.
Avdi
BTW, any reason you're not just using `FruitKinds.find_first_by_fruit_name("apple")` for the find?
Avdi
This is Rails 2.2, and I think that was introduced in 2.3, but I'm not sure. I tried it and it didn't seem to work, and there's no analogously named find_first_by_* methods.
Kyle Kaitan
The find_by_* methods are generated automatically. See http://api.rubyonrails.org/classes/ActiveRecord/Base.html under "Dynamic attribute-based finders". They have been available for quite a while, you should be fine in 2.2.
Avdi
In any case at least consider doing `FruitKinds.find(:first, :conditions => { :fruit_name => "apple" })` or something along those lines (my AR is a little rusty). That `find(:all)` hurts me to look at.
Avdi
A: 

As Avdi mentioned, you'll want to avoid interacting with the database when the class is loaded. If you're wanting to cache database records in local memory, I recommend using the Memoization feature added in Rails 2.2. See my post here for details.

ryanb