@ledsun blog

無味の味は佳境に入らざればすなわち知れず

オートローダー設計調査

ruby.wasmでオートロードする - @ledsun blogModule#const_missingをつかってオートローダーを書いてみました。 Module#const_missingAPIで、汎用的なオートローダーを書くのは少し難しそうなことがわかりました。 難しそうですが、どれくらい難しいのかよくわかっていません。 そこで、先人の知恵に頼ります。

具体的には、Rails 5.2 から6.0でオートローダーをclassicからzeitwerkに変更した出来事を調べます。 Rails 5.2までのオートローダーはModule#const_missingを使って実装されています。 Rails 6.0以降のオートローダーzeitwerkはKernel.#autoloadを使って実装されています。 なぜでしょうか?

ClassicローダーからみるModule#const_missingを使ったオートローダーの問題点

classicローダーのModule#const_missingを使った実装は、おおむね動いていましたが細かい問題がありました。 わかりやすい例は Understanding Zeitwerk in Rails 6 | by Marcelo Casiraghi | Cedarcode | Medium で、説明されています。

次のような例が挙げられています。

# app/models/user.rb
class User < ApplicationRecord
end

# app/models/admin/user.rb
module Admin
  class User < ApplicationRecord
  end
end

# app/models/admin/user_manager.rb
module Admin
  class UserManager
    def self.all
      User.all # Want to load all admin users
    end
  end
end

UserManager内でUserを呼び出したときに、Module#const_missingでは、呼び出された物がUserかAdmin::Userかわかりません。 Module#const_missingの引数には、どちらの場合であってもシンボル:Userが渡されます。

その他の多くの問題がRails 5.2のRailsガイドで列挙されています。 Module#const_missingでオートローダーを実装するには、とても多くの問題があるようです。

Zeitwerkのとった解決方法

これらの問題を解決するためにzeitwerkはKernel.#autoloadを使って再実装されました。 Kernel.#autoloadはモジュールを読み込むパスを指定できます。 モジュールが必要になったときに指定したパスをつかってKernel.#autoloadします。 つまり、オートロードです。 正確にはオートロードに使うヒントをRuby VMに渡します。

zeitwerkでは、起動時にsetupメソッドを使ってルートディレクトリからモジュールのファイルパスを収集し、Kernel.#autoloadを呼び出します。 Kernel.#autoloadを使うには、モジュール呼び出しより前に、モジュールの実体ファイルのパスを調べます。

ruby.wasmへの応用を考える

ruby.wasmでは、この方式は厳しいです。 サーバーに対してパス探索をかけたくありません。 自動的にモジュールのパスを集めるのは難しいです。

さらに前の段階でモジュールパスを調査しておいて、import mapsのようにブラウザに渡せばできるでしょう。 ですが、オートロードがうれしいのは設定が要らない点です。設定より規約(Convention over Configuration、CoC)です。 マップを作ってブラウザに渡すのは設定です。 Kernel#require_relativeを各Rubyスクリプトファイルに書くのと同じです。

参考