Rake ignores eager loading Rails config

Some days ago I got bit by an unexpected behavior from rake on a Rails app. Join me in this little trip around rake tasks and Rails configuration!

The setup

On a Rails project I'm working on, I recently had to write a script to migrate some data in the database. This is an approximation to what my code looks like:

class Parent < ApplicationRecord
end

class ChildA < Parent
end

class ChildB < Parent
end

That is, we use Single Table Inheritance to hold records of different classes in the same database table. Here the Parent class is not supposed to be used directly, so we won't expect any Parent records. We'll only have ChildA and ChildB records.

In my use case, we recently added a new column to that table and we had to fill it for already-existing records, so I wrote a rake task:

# lib/tasks/my_task.rake

namespace :foo do
  desc "Test task"
  task bar: :environment do
    Parent.where(my_column: nil).find_each do |record|
      record.fill_my_column!
    end
  end
end

I'm taking advantage of Rails' STI: when you load a bunch of records from the database using the parent class, they're automatically casted to the correct class. So, in this case, I'd have a bunch of ChildA and ChildB records, and no Parent records.

Checking that the code works

I deployed my code to production, fired the rails console and ensured we had some records that would be affected by the rake task:

> Parent.where(my_column: nil).count
20
> Parent.where(my_column: nil).pluck(:type).uniq
["ChildA", "ChildB"]

Nice! So I had 20 records that would be affected by my rake task, and all of them were of the expected types. So I ran the rake task:

$ rails foo:bar
# no output

Everything looked good, so I fired again the rails console and re-checked that everything worked as expected:

> Parent.where(my_column: nil).count
20
> Parent.where(my_column: nil).pluck(:type).uniq
["ChildA", "ChildB"]

Wait... what? Why am I getting the same results? Didn't the rake task run? What's going on?

Rake won't eager-load

Yes, you read that right. Rake will ignore the eager-loading config. In a typical Rails app, you'll probably have this line:

# config/environments/production.rb
config.eager_load = true

This is the default Rails behavior on production environment. This makes Rails preload all classes in memory when booting the app for a better performance... but Rake tasks will ignore this setting. There's a pull request to Rails from February 2017 to be able to control this setting, but it's currently scheduled for the 6.1.0 version of Rails.

So, how to solve this problem? A few options come to mind:

  • Run the script from the rails console. The console will consider the eager-loading setting, so we won't hit this problem.
  • Call the classes you'll need before running the actual task code. By calling the classes, they will get loaded and the code will work, but you need to know all the classes you need. If your code depends on a lot of classes, or you can't know the whole list before-hand for some reason, this solution won't work.

I went with the first solution: I copied the task code and ran it from the Rails console, as it was a one-time task that could be removed right after being executed.

I hope this will help you understand the problem so that you have a way to overcome it if you ever encounter it!

Cover picture by Julian Hochgesang on Unsplash