背景
社内のマイグレーションファイルにて、def up
とdef 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
が呼ばれる。
これ以降はup
もself.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
を実行したときに、マイグレーションクラスのインスタンスが返るようになります。