この記事は CAMPFIRE Advent Calendar 2022 8日目の記事です。
アドベントカレンダー参加という良い機会に、ふと気になったRailsの自動読み込み(autoload)について調べてみました。
前提
以下の環境で検証しています
- OS: macOS
- Rubyバージョン: 2.7.6
- Railsバージョン: 7.0.4
- Railsオートローダ: Zeitwerk
また、Railsガイド の表現に基づきautoload
を「自動読み込み」として称しています
Railsの自動読み込み(オートロード)
Railsで利用されているオートローダはRails6以降のバージョンで変更され現在はZeitwerkというgemに任されています
Rails5以前のオートローダは「classic」と称され、Rails6.x の場合のみ、classicとzeitwerkモードの切り替えが可能です
参考: 4 zeitwerkモードを有効にする
バージョン | オートローダ | モードの変更 |
---|---|---|
Rails 5以前 | Active Support で実装 (classic ) |
classicのみ |
Rails 6.x | Zeitwerk (zeitwerkモード ) |
classicに変更可能 |
Rails 7 | Zeitwerk (zeitwerkモード ) |
zeitwerkモードのみ |
classic と zeitwerk モード の違い
2つの違いについて詳細は以下の記事で詳しく記載されています
参考:Zeitwerkの壊し方
大きな違いはファイル名とクラス(やモジュール)の紐付け方法で以下のような違いがあります
- classic:
定数名から
ファイルを探す - Zeitwerk:
ファイル名
から定数を推測する(ファイル名をキャメルケースに変換する)
このため、classicから Zeitwerkに以降すると 新ルールに則っていないクラスは場合にcamelizeに失敗します。
例) html_parser.rb
はHTMLParser
で認識できずHtmlParser
でないと動かなくなる
Zeitwerk
これからのRailsで使われていくZeitwerk
これを知れば、Railsの自動読み込みを理解できる…!
ということでZeitwerkでできることを紹介しようと思っていたのですが
ちょうど4日前にとてもわかりやすい日本語記事が公開されていました。
Zeitwerk を使いこなしたい
とても丁寧にかかれていて、 上記の記事とZeitwerkのReadme を合わせて読めばかなり理解が進むと思います!
Railsでのカスタマイズ
自動読み込みの対象となる ディレクトリを追加したい場合は configで設定が可能です
参考: Railsガイド 定数の自動読み込みと再読み込み (Zeitwerk)
詳細な設定
また、config/initializers/
にファイルを追加し、
Zeitwerk の設定を記載することもできます
参考: 定数の自動読み込みと再読み込み (Zeitwerk) 10 活用形をカスタマイズする
例1) 特定のディレクトリを除外する
# config/initializers/zeitwerk.rb
Rails.autoloaders.each do |autoloader|
autoloader.inflector = Zeitwerk::Inflector.new
# app/settings を除外
autoloader.ignore "app/settings"
end
例2) html_parser.rb のクラスを HTMLParser として利用する(通常はHtmlParser)
# config/initializers/zeitwerk.rb
Rails.autoloaders.each do |autoloader|
autoloader.inflector = Zeitwerk::Inflector.new
#案1)
# autoloader.inflect.acronym "HTML"
# 案2)
autoloader.inflector.inflect(
"html_parser" => "HTMLParser",
)
end
Railsの自動読み込みにオリジナルの変換処理を追加する
本題です。
更に自由な変更を加えたい場合、インフレクタ(inflector)をオリジナルのインフレクタに変更することでより自由に修正を入れることができます
※基本的にはデフォルトのインフレクタの利用したほうが後々混乱も誘発せず望ましいと思います。
手順
MyInflector
を作成して上記の変換の処理を実装する場合
1. オリジナルのインフレクタ作成
Zeitwerk::Inflector
を継承してオリジナルのインフレクタを作成します
#lib/inflector/my_inflector.rb
class MyInflector < Zeitwerk::Inflector
def camelize(basename, abspath)
if basename =~ /\Ahtml_(.*)/
"HTML" + super($1, abspath)
else
super
end
end
end
2. 作成したインフレクタを読み込み、オートローダに設定
autoloader.inflector = MyInflector.new
で自動読み込みで利用するインフレクタを設定します
# config/initializers/zeitwerk.rb
require 'inflector/my_inflector'
Rails.autoloaders.each do |autoloader|
autoloader.inflector = MyInflector.new
end
解説: def camelize について
パラメータ
MyInflectorのcamelize
メソッドには以下のパラメータが渡され、これらを元にクラス名やモジュール名の変換が可能になります
- basename: 拡張子を含まないファイル名
- abspath: ファイルやディレクトリの絶対パス
例) {PATH}/app/controllers/application_controller.rb basename: application_controller abspath: {PATH}/app/controllers/application_controller.rb
変換の処理
if basename =~ /\Ahtml_(.*)/
"HTML" + super($1, abspath)
「html_」から始まるファイルの「html_」をHTMLにし、残りはそのままキャメルケースに変換している
例えば「HTML」を「YML」のように変更した場合は「html_perser.rb」というファイルのクラスを「YMLParser」として利用できます
if basename =~ /\Ahtml_(.*)/
"YML" + super($1, abspath)
最後に
これらを応用すればより柔軟なクラスやモジュール名の変更が可能です。
どうしてもconfigだけでの解決ができない場合検討してはいかがでしょうか?