RoR: MyModel.descendants returns [] in a view afte

2020-03-01 20:47发布

问题:

I want to display a selection list of MyModel subclasses in a view. It's not working yet, so for sanity checking, I included this in my view:

<%= MyModel.descendants %>

The first time I render this page after re-starting the server, it shows the list of descendants (there are six). All subsequent times, it shows up as an empty list [].

FWIW, I have a require statement in my initializers:

Dir[Rails.root.join("app/models/my_models/**/*.rb").to_s].each {|f| require f}

... and I've verified that they're getting required.

What the @($%& is going on?

回答1:

When you use require, even if your my_model.rb gets reloaded, the kernel won't require your subclasses .rb files because they have already been loaded. You'd have to go through rails autoload.

Basically, on your first request, rails autoloads MyModel from my_model.rb, which then requires my_models/sub_model.rb. The SubModel class inherits MyModel, which populates the descendants array. On your following requests, though, rails autoloads MyModelagain (hey, you're in dev mode), which then requires my_models/sub_model.rb again. But this time, the kernel knows it had already loaded this file and won't load it again.

I ran into this problem one hour ago, which lead me to your post, and to find a solution. What we need is rails to autoload subclasses everytime your main class is called.

Here is a solution :

class MyModel
  Dir[File.join(File.dirname(__FILE__),"my_models","*.rb")].each do |f|
    MyModels.const_get(File.basename(f,'.rb').classify)
  end
end

These lines could probably be put outside of the class. That should be enough (it is for me) if you only have files in my_models and not in subdirectories. If you have some (for example MyModels::Car::Ford, you may need to put the same kind of stuff in the submodules (in my_models/car.rb).



回答2:

I had the same problem. Solved it by adding a config/initializers/preload_models.rb with:

Dir[Rails.root + 'app/models/*.rb'].map {|f| File.basename(f, '.*').camelize.constantize }

Hope that helps somebody.



回答3:

I just enabled eager loading in each environment:

config.eager_load = true

This worked for me even when using namespaces for class names.