ActiveRecord::Migration prepend context and method

Viewed 163

I'm building up a library to add a functionality to ActiveRecord::Migration but I'm struggling to understand the behavior.

When the library is loaded, I execute the following code

ActiveSupport.on_load(:active_record) do
  ActiveRecord::Migration.prepend MyLibrary::Mutators
end

Then in my_library/mutators.rb

module MyLibrary
  module Mutators
    def do_something
      # do stuff here and use `self`
    end
  end
end

The goal here is very simple, I need to be able to call this method within my migrations classes

class Test < ActiveRecord::Migration[5.2]
  do_something

  def change
    create_table 'async_test' do |t|
      t.string :test
    end
  end
end

When I run this migration, it'll effectively call do_something

The problem is when I try to get some context about what is run, which's necessary for my library to execute other stuff, self is an instance of ActiveRecord::Migration and not Test, but the class using this method is Test.

#<ActiveRecord::Migration:0x00007fac5b83df38
 @connection=nil,
 @name="ActiveRecord::Migration",
 @version=nil>

If I change things around and call do_something within #change it'll consider self as the instance of Test which's what I would have liked on class level.

How can I get do_something to return self as Test at class level by extending ActiveRecord::Migration ?

1 Answers

ActiveRecord::Migration.method_missing (on class level) calls nearest_delegate that seems to be an instance of ActiveRecord::Migration

 > ActiveRecord::Migration.nearest_delegate
 => #<ActiveRecord::Migration:0x0000561889aa1930 @connection=nil, @name="ActiveRecord::Migration", @version=nil>

When you call ActiveRecord::Migration.prepend MyLibrary::Mutators you prepend instance methods of ActiveRecord::Migration with MyLibrary::Mutators. so do_something is defined on an migration instance.

When you call:

class Test < ActiveRecord::Migration[5.2]
  do_something
  # ...
end

Test.method_missing is called, it calls #do_something on nearest_delegate that appears to be ActiveRecord::Migration instance.

If you want to have do_something really defined on migration class level, you need to properly prepend class methods. It is described precisely in answers to this question.

Long story short, you should call .prepend in migration singleton class, not on migration class:

ActiveSupport.on_load(:active_record) do
  ActiveRecord::Migration.singleton_class.prepend MyLibrary::Mutators
end
Related