3
0

More than 3 years have passed since last update.

マイグレーションファイルのdef upとdef self.upの違いについて調べてみた

Last updated at Posted at 2019-12-18

背景

社内のマイグレーションファイルにて、def updef self.upが混在していたのでどちらかに統一すべく(そもそも統一すべきかどうかも含めて)、違いを調べてみた。
(正確には、調べてみたけどまだ途中です)

環境

ruby 2.6.3
rails 5.2.3

やり方

適当なマイグレーションファイル内にbinding.pryを埋め込んで挙動を確認する。

def up

class CreateUsers < ActiveRecord::Migration[5.2]
  def up
binding.pry    # ここに埋め込む
    create_table :users do |t|
      t.string :name

      t.timestamps
    end
  end
end

実行

$ rails db:migrate
== 20191020160206 CreateUsers: migrating ======================================

    2:   def self.up
    3: binding.pry
 => 4:     create_table :users do |t|
    5:       t.string :name
    6: 
    7:       t.timestamps
    8:     end
    9:   end

pry(CreateUsers)> step  # nextだとcreate_tableの実行後に移動してしまうのでstepでcreate_tableの中に入る

    603: def method_missing(name, *args, &block) # :nodoc:
 => 604:   nearest_delegate.send(name, *args, &block)
    605: end

pry(CreateUsers)> self
=> CreateUser

pry(CreateUsers)> nearest_delegate   # CreateUsersクラスのインスタンスが格納されている
=> <##<CreateUsers:0x00007fbc300761f8
 @connection= #<ActiveRecord::ConnectionAdapters::SQLite3Adapter:0x00007f9f74064350 .....>
 @name="CreateUsers"
 @version=2019102016020

pry(CreateUsers)> name
=> :create_table

pry(CreateUsers)> args
=> [:users]

pry(CreateUsers)> block
=> #<Proc:0x00007fbc30176c10@/home/ec2-user/environment/sample/db/migrate/20191020160206_create_users.rb:4>

CreateUsersクラスのインスタンス(nearest_delegate)がcreate_tableを実行。

pry(CreateUsers)> step

    858: def method_missing(method, *arguments, &block)
 => 859:   arg_list = arguments.map(&:inspect) * ", "
    860: 
    861:   say_with_time "#{method}(#{arg_list})" do
    862:     unless connection.respond_to? :revert
    863:       unless arguments.empty? || [:execute, :enable_extension, :disable_extension].include?(method)
    864:         arguments[0] = proper_table_name(arguments.first, table_name_options)
    865:         if [:rename_table, :add_foreign_key].include?(method) ||
    866:           (method == :remove_foreign_key && !arguments.second.is_a?(Hash))
    867:           arguments[1] = proper_table_name(arguments.second, table_name_options)
    868:         end
    869:       end
    870:     end
    871:     return super unless connection.respond_to?(method)
    872:     connection.send(method, *arguments, &block)
    873:   end
    874: end

pry(#<CreateUsers>)> self   # nearest_delegateと同じ
=> #<CreateUsers:0x00007fbc300761f8
 @connection= #<ActiveRecord::ConnectionAdapters::SQLite3Adapter:0x00007f9f74064350 .....>
 @name="CreateUsers"
 @version=2019102016020

pry(#<CreateUsers>)> method
=> :create_table

pry(#<CreateUsers>)> arguments
=> [:users]

create_tableのレシーバはCreateUsersであり、CreateUsersの親クラス(ActiveRecord::Migrationなど)や読み込んでいるモジュールのクラスメソッドにもないっぽいので、クラスメソッドのmethod_missingが呼ばれる。

def up

とりあえず実行

$ rails db:migrate
== 20191020160206 CreateUsers: migrating ======================================

From: /home/ec2-user/environment/sample/db/migrate/20191020160206_create_users.rb @ line 4 CreateUsers#up:

    2:   def up
    3: binding.pry
 => 4:     create_table :users do |t|
    5:       t.string :name
    6: 
    7:       t.timestamps
    8:     end
    9:   end

pry(#<CreateUsers>)> self
=> #<CreateUsers:0x00007f8f04070c70
 @connection= #<ActiveRecord::ConnectionAdapters::SQLite3Adapter:0x00007f9f74064350 .....>
 @name="CreateUsers",
 @version=20191020160206>

pry(#<CreateUsers>)> step

From: /home/ec2-user/environment/rails/activerecord/lib/active_record/migration.rb @ line 859 ActiveRecord::Migration#method_missing:

    858: def method_missing(method, *arguments, &block)
 => 859:   arg_list = arguments.map(&:inspect) * ", "
    860: 
    861:   say_with_time "#{method}(#{arg_list})" do
    862:     unless connection.respond_to? :revert
    863:       unless arguments.empty? || [:execute, :enable_extension, :disable_extension].include?(method)
    864:         arguments[0] = proper_table_name(arguments.first, table_name_options)
    865:         if [:rename_table, :add_foreign_key].include?(method) ||
    866:           (method == :remove_foreign_key && !arguments.second.is_a?(Hash))
    867:           arguments[1] = proper_table_name(arguments.second, table_name_options)
    868:         end
    869:       end
    870:     end
    871:     return super unless connection.respond_to?(method)
    872:     connection.send(method, *arguments, &block)
    873:   end
    874: end

create_tableのレシーバがインスタンスになったので、インスタンスメソッドのmethod_missingが呼ばれる。

これ以降はupself.upも同じ(だと思う)。

結論

  • self.upの場合はクラスメソッドのmethod_missingが呼ばれ、その後にレシーバにインスタンスを指定した後、再度インスタンスメソッドのmethod_missingが呼ばれる。
  • upの場合はインスタンスメソッドのmethod_missingだけが呼ばれる。


いやいや


nearest_delegateってなんなの

nearest_delegate

module ActiveRecord



 class Migration



   class << self
     attr_accessor :delegate # :nodoc:
     attr_accessor :disable_ddl_transaction # :nodoc:

->  def nearest_delegate # :nodoc:
       delegate || superclass.nearest_delegate
     end

accessor :delegateがある。あと下の方に、

   def initialize(name = self.class.name, version = nil)
      @name       = name
      @version    = version
      @connection = nil
    end

    # instantiate the delegate object after initialize is defined
    self.delegate = new

とかあったので、今回の場合、

CreateUsers.delegate = CreateUsers.new(name : "CreateUsers", version: 2019102016020, connection: #<ActiveRecord::ConnectionAdapters::SQLite3Adapter:0x00007f9f74064350 .....>)

ということ?

結び

引き続き調査します。

追記(2019/12/25)

self.upと書いた場合には、method_missingの前に、マイグレーションクラスのインスタンスが下記を実行します。

ActiveRecord::Migrator#up

def up
  self.class.delegate = self
  return unless self.class.respond_to?(:up)
  self.class.up
end

self.class.delegate = selfによって、マイグレーションクラスがnearest_delagateを実行したときに、マイグレーションクラスのインスタンスが返るようになります。

3
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
0