views:

36

answers:

1

I have a Rails app representing a card game. There are several different types of card, and a few expansions for the game, each providing more cards. So I'm using a single cards table and STI, with each expansion having a models subdirectory and appropriately-named module. Like this:

app/models
- card.rb
+ base_game
  - foo.rb
    - class BaseGame::Foo < Card
  - bar.rb
    - class BaseGame::Bar < Card
+ exp_one
  - thing.rb
    - class ExpOne::Thing < Card

This works well.

I would like to be able to get a list of all the cards in each expansion. The constants method of Module provides a neat way of doing this -

class Card
  def self.base_game_cards 
    BaseGame.constants.map {|c| ("BaseGame::" + c).constantize}.select {|c| c.instance_of?(Class) and c.superclass == Card}
  end
end

...except that Rails only loads a given model definition when it's first referenced, so Card.base_game_cards is usually [].

I've tried working round this by forcing a preload of all the models, by adding a file into config/initializers:

preloader.rb:

# Preload all card models from all expansions. This allows us to dynamically
# determine card types by reflection.
Dir.glob("#{RAILS_ROOT}/app/models/*/*.rb").each do |file| 
  require file
end

But this doesn't seem to work either, and I can't work out why. By use of raise and inpect, I've determined that when I call Card.base_game_cards from a controller, the result is still usually []. Very occasionally it's correct just after server restart, but refreshing the page sends it back to []. If I use script\console, the list is always correct. I've tried moving the block of requires to the top of the controller class definition, but that makes no difference either.

Now, I'm perfectly willing at this stage to go for another solution (probably just inflecting the list of filenames returned by glob), but I'd at least like to know why this method isn't working. Can anyone shed some light (or - even better - make it work)?

+1  A: 

yeah unfortunately active_support is "on demand" not "pre-demand" so you'll probably have to use your preloader. Unfortunately if your app is in development mode it's probably set to "reload classes with each request" so you'll have to disable that convenient feature of rails, or make sure you re-load those files when needed.

-r

rogerdpack
Ah, I hadn't considered the "reload on request" aspect. I'll try the preloader with that off and see what happens.
Chris
Ah, yes. If I turn off cache_classes, I get this technique working consistently. Thanks!
Chris
you should be able to force their reloading by using the classname Preloader where it is needed, then you don't have to turn off cacheing...or through some means like that, I believe.
rogerdpack