Creating a model registry

Hi all,

This is a problem I’ve approached so many times and always worked
around, that now I want to solve it once and for all. Say I have
something like this:


class X < ActiveRecord::Base
acts_as_wacky
end

module Wackinator
class ControlAllWackos
@@wackos = []
def self.kill_wackos
@@wackos.each(&:kill)
end
end

def acts_as_wacky
ControlAllWackos.wackos << self
# do stuff
end
end

I know that code won’t actually run, but hopefully it serves to
illustrate my point.

Which is that since Rails loads models with load_missing_constant, you
can’t count on X having been defined, and therefore you can’t count on
ControlAllWackos.wackos being accurate.

Ok, so there’s a couple possible solutions:

  • load all your models right away with Dir.glob(File.join(RAILS_ROOT,
    ‘app’, ‘models’, ‘*.rb’)).each{|f|require f}

this is problematic with cache_classes in development mode, because
cache_classes does some voodoo around unloading classes, and you end
up with lots of errors.

  • statically define @@wackos:

module Wackinator
class ControlAllWackos
@@wackos = %w(X Y Z)
def self.kill_wackos
@@wackos.map(&:classify).each(&:kill)
end
end

def acts_as_wacky
# do stuff
end
end

which is difficult to maintain, and ugly too.

This is kind of a chicken-and-egg problem, and I don’t anticipate
finding a better solution than the static definition. But I thought
some of ye ruby wizzards might know some arcana that might be of use
here.

Thanks!
-Ian

I. E. Smith-Heisters wrote:


class X < ActiveRecord::Base
acts_as_wacky
end

module Wackinator
class ControlAllWackos
@@wackos = []
def self.kill_wackos
@@wackos.each(&:kill)
end
end

def acts_as_wacky
ControlAllWackos.wackos << self
# do stuff
end
end

require ‘wackinator’
class X < ActiveRecord::Base

hth

ilan

On 3/7/08, Ilan B. [email protected] wrote:

class ControlAllWackos
end

require ‘wackinator’
class X < ActiveRecord::Base

hth

ilan

But that code won’t be evaluated until the model X is used somewhere,
which is totally indeterminate. So if you boot your app and right away
call Wackinator::ControlAllWackos.kill_wackos, chances are X hasn’t
been loaded and @@wackos will be empty. Or am I missing something?

Thanks,
Ian

Hm, yes… but then you can’t test your code. Given that choice, I’d
rather have a hard-to-maintain static list. Perhaps if I look further
into why things don’t work in development I can find a way of fixing
it. But that still wouldn’t address the possibility of there being a
model defined somewhere else than app/models, which I guess is a
sufficiently rare case to ignore.

What about using the Dir.glob trick only through initializers for
production.rb?

Very interesting… thanks for the follow up.

Ok, looking into cache_classes led me to this solution, which seems to
work:

in config/environment.rb:

silence_warnings do
Dir.glob(File.join(RAILS_ROOT, ‘app’, ‘models’,
‘*.rb’)).each{|m|require_or_load m}
end

elsewhere:

module Wackinator
class ControlAllWackos
@@wackos = []
def self.kill_wackos
@@wackos.each(&:kill)
end
end

def acts_as_wacky
ControlAllWackos.wackos << self

do stuff

end
end

The important part is that you have to use require_or_load instead of
require so that eagerly loading all the models works in development
mode. From there you can do whatever you want, in my case I’m
registering all my wackos so that they’re available in
ControlAllWackos.wackos.

Work for me at the moment…

-Ian