Rake ignores eager loading Rails config
I recently hit an unexpected behavior: Rake was not eager-loading classes, even though my Rails config said so. Here's why!
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