Why does ActiveRecord still support class level migrations?

So migrations look like this nowadays:

class MyMigration < ActiveRecord::Migration
  def change
    create_table :my_table ...
  end
end

To my surprise, the following actually works too!

class MyMigration < ActiveRecord::Migration
  create_table :my_table ...
end

This was a production bug at my company because people make mistakes and there were no automated tools to catch this error. It leads to inconsistent behavior because

  1. It only gets run at the class load time
  2. It can’t be rolled back

After some researching, I found that this was the way to do migrations < Rails 3.1. And on Rails 3.1 we got instance method support: converted migrations to support instance methods · rails/rails@8b2f801 · GitHub but class methods were not deprecated.

My question is: what’s the best way to prevent this error from happening?

  • Deprecate class method support in future Rails?
  • Some custom rubocop rule?
  • Some kind of automated test?
1 Like

This truly is a WTF! I’ve spent a lot of time digging around in exactly this area of the framework, and this still surprised me.

The old style of migrations still used up and down, but they were class methods instead. The commit you referenced is the point at which it changed to instance methods.

The fact that create_table works at the class level was never intentional, I don’t think. It’s a side-effect of the method_missing delegation to an instance of the Migration, which forwards method_missing to the database connection.

That’s necessary to allow all the migration helpers to work, but I wonder if we can’t deprecate and remove this singleton method_missing, or at least tighten up the message forwarding somehow.

2 Likes

Based on some preliminary poking around in the test suite, I think the framework would actually have to remove support for singleton up and down entirely before this could be fixed.

You have to be able to reference any schema helpers at the class level because you can still define up and down at the class level.

This one is extra wrinkly due to the backwards-compatibility needs around migrations. We could change ActiveRecord::Migration[6.2] to avoid this but there’s no way to remove this behavior from ActiveRecord::Migration[5.0].

1 Like