Skip to main content

Rails Gotcha: Eager Loading Application Classes to Make STI Work

Blog post   •   Apr 28, 2009 13:12 GMT

Recent versions of Rails (2.2 and later I think) will eager load all application Ruby classes when booting up the server. However, because of issues with migrations this is not done if you are executing a Rake task that depends on the :environment task (such as db:migrate). It is also not done if cache_classes is set to true, a setting that you have by default in development to allow you to change your Ruby classes and not have to restart the server to see the result. It turns that the lazy loading of application classes in development can lead to breakage of ActiveRecords STI (Single Table Inheritance) support. In our case we had an STI with YouTubeChannel that inherits from ApiChannel and we ran into this behaviour:

>> ApiChannel.send(:subclasses)
=> []
>> ApiChannel.find(1)

# ApiChannel Load (2.5ms)   SELECT * FROM "channels" WHERE ("channels"."id" = 1) AND ( ("channels"."type" = 'ApiChannel' ) )
ActiveRecord::RecordNotFound: Couldn't find ApiChannel with ID=1

>> YouTubeChannel
=> YouTubeChannel(...)
>> ApiChannel.send(:subclasses)
=> [YouTubeChannel...]
>> ApiChannel.find(1)

#  ApiChannel Load (0.9ms)   SELECT * FROM "channels" WHERE ("channels"."id" = 1) AND ( ("channels"."type" = 'ApiChannel' OR "channels"."type" = 'YouTubeChannel' ) )
=> #<YouTubeChannel id: 1...

So what is happening here is that since YouTubeChannel hasn't been loaded Rails doesn't know that it exists and ApiChannel.subclasses is empty and ActiveRecord (construct_finder_sql - add_conditions! - type_condition) cannot construct the proper SQL condition.

I'm not sure what the reason is that classes are not eager loaded in development (other than the obvious tomake the server startup time shorter), but for now I have patched the Rails::Initializer like this:

Rails::Initializer.class_eval do
# Allows us to run rake jobs:work in development
def is_delayed_job_rake_task?
$0 =~ /rake$/ && ARGV.detect { |arg| arg =~ /^jobs:/ }

# Eager load application classes
def load_application_classes

return if ($rails_rake_task && !is_delayed_job_rake_task?)
if configuration.cache_classes
configuration.eager_load_paths.each do |load_path|
matcher = /\A#{Regexp.escape(load_path)}(.*)\.rb\Z/
Dir.glob("#{load_path}/**/*.rb").sort.each do |file|
require_dependency file.sub(matcher, '\1')

Comments (3)

    It turns out my patch didn't solve the problem since ActiveSupport::Dependencies will clear out loaded constants on every request. I therefore need to patch the Dispatcher or Dependencies to eager load. Something crazy like this:

    ActiveSupport::Dependencies.module_eval do
    def clear_with_reload

    alias_method_chain :clear, :reload

    - peter - Apr 29, 2009 14:12 GMT


    Thanks for such a knowledgeable post.

    <a href="">Airul</a>

    - Market Research Vietnam - Apr 26, 2016 13:58 GMT


    Thanks for such a knowledgeable post.

    - Kopi Hijau - Jun 09, 2016 03:57 GMT

Add comment