Ruby on Rails

Zeitwerkとは?読み方とRailsの自動読み込みの仕組み・設定を解説

Zeitwerk(ツァイトヴェルク)は、Ruby向けのコードローダーです。ファイル名と定数名の対応を規約に沿って解決し、requireを書かずにクラスやモジュールを読み込みます。Rails 6.0で標準採用され、それまでのClassicモードはRails 7.0で削除されました。本記事では読み方と語源、命名規則による自動読み込みの仕組み、Rails 8での設定、Rails以外の素のRubyでgemとして使う方法、つまずきやすいエラーの対処までを扱います。

まとめ

  • Zeitwerkは「ファイル名=定数名」の規約で動くRubyのコードローダー。命名とディレクトリ構造を合わせればrequireは不要になる。
  • 読み方はツァイトヴェルク(ドイツ語で「時」+「仕掛け・機構」の意)。同名の高級時計とは無関係の、Xavier Noria氏によるgemを指す。
  • Rails 6.0で標準化。Classicモードとconfig.autoloader=はRails 7.0で削除されたため、現行のRails 8では「Classicへ戻す」設定は存在しない。
  • Railsではconfig.load_defaultsで有効化され、リロード制御はenable_reloading(旧cache_classes)が担う。移行検証はbin/rails zeitwerk:check
  • Rails外でもZeitwerk::Loaderを直接生成すれば、素のRubyプロジェクトやgem開発で自動読み込みを使える。

Zeitwerkの読み方・語源とコードローダーとしての役割

Zeitwerkは、規約に沿ったファイル構成を前提に、Rubyのクラス・モジュールを必要な時に読み込む(オートロード)、または起動時に一括で読み込む(イーガーロード)ためのライブラリです。作者はRailsコミッターのXavier Noria(GitHub: fxn)氏で、GitHubでは「Efficient and thread-safe code loader for Ruby(効率的でスレッドセーフなRuby用コードローダー)」と説明されています。スレッドセーフに設計されており、マルチスレッド環境でも定数の読み込みが競合しません。

読み方はツァイトヴェルクです。ドイツ語の Zeit(時間)と Werk(作品・仕掛け・機構)を組み合わせた語で、「時計仕掛け」に近いニュアンスを持ちます。検索では高級時計ブランドA. Lange & Söhneの「ZEITWERK」も同じ綴りでヒットしますが、本記事で扱うのはRuby/Railsのgemであり、時計とは別物です。開発の文脈で「Zeitwerk」と出てきたら、まずこのコードローダーだと考えて差し支えありません。

命名規則とファイル対応:自動読み込みの仕組み

Zeitwerkの動作は「ファイル名と定数名を1対1で対応させる」という一点に集約されます。ファイル名はスネークケース、対応する定数はキャメルケースに変換され、ディレクトリはそのまま名前空間(モジュール)として扱われます。

ファイル 期待される定数
app/models/user.rb User
app/models/admin_user.rb AdminUser
app/services/payment/processor.rb Payment::Processor

この規約から外れると、読み込み時に定数未定義(NameError)が発生します。ファイル名が定数名と食い違っている、あるいはディレクトリ階層と名前空間がずれているケースが典型です。

autoload・eager load・reloadの3つの動作

Zeitwerkは目的の異なる3つの読み込み方を提供します。オートロードは参照された定数を都度読み込み、イーガーロードは対象ディレクトリのファイルを起動時にすべて読み込みます。リロードは、開発中に編集したコードをプロセス再起動なしで再読み込みする機能です。本番では安定性のためイーガーロードを使い、リロードは無効にします。

Rails環境ごとの挙動の違い

Railsではこの3動作が環境設定に紐づいています。開発環境はリロード有効・イーガーロード無効で、編集が即時反映されます。テスト環境はイーガーロード無効で必要分だけ読み込み、CIではeager_loadを有効にして全定数の読み込み可否を検査するのが定石です。本番環境はイーガーロード有効で、リクエストごとの読み込み遅延を避けます。

ClassicモードとRails 7以降の現状

Rails 6.0以前のClassicモードは、autoload_pathsに登録したディレクトリから定数名を手掛かりにファイルを探す方式でした。ファイル名と定数の対応が緩く、読み込み順によっては未定義エラーや二重読み込みが起きるという課題がありました。Zeitwerkはこの対応を厳格化し、挙動を予測可能にしています。

重要なのは、ClassicモードはすでにRails本体から削除済みだという点です。Rails 6.0・6.1は移行のため両モードを併存させていましたが、Rails 7.0でClassicモードとconfig.autoloader=設定が削除されました。つまり現行のRails 8には「Classicへ切り替える」選択肢自体が存在しません。古い記事にあるconfig.autoloader = :zeitwerkという記述は、Rails 7以降では不要(かつ設定項目として無効)です。既存アプリをアップグレードする場合は、Classicへ戻すのではなく、Zeitwerkの規約にコードを合わせることが前提になります。命名規約を全面的に変えられないレガシー資産では、規約整備の工数が移行の便益を上回る場面もあるため、その場合はRailsのバージョンをZeitwerk併存期(6.1)で止め、段階的に直すほうが現実的です。

RailsでのZeitwerkの設定と移行チェック

新規のRailsアプリはZeitwerkが既定で有効なため、追加設定は要りません。挙動の基点になるのはconfig/application.rbload_defaultsです。


# config/application.rb
config.load_defaults 8.0

リロードの制御は、Rails 7.1でcache_classesから改称されたenable_reloadingで行います。開発環境ではtrue、本番ではfalseが既定です(cache_classesも後方互換で残りますが、新しいコードではenable_reloadingを使います)。


# config/environments/development.rb
config.enable_reloading = true
config.eager_load = false

lib配下は、Rails 7.1以降autoload_libで自動読み込み対象に加えられます。assetstasksなど定数を持たないディレクトリは除外します。


# config/application.rb
config.autoload_lib(ignore: %w[assets tasks])

既存アプリの移行時は、規約に沿っているかを次のコマンドで検査します。全ファイルをイーガーロードして定数解決を試すため、ずれているファイルがその場で報告されます。


bin/rails zeitwerk:check

読み込み経路を追いたいときは、ロガーを有効にするとどのファイルがいつ読み込まれたかが出力されます。


Rails.autoloaders.log!

Rails外の素RubyでのZeitwerk利用

Zeitwerkは独立したgemであり、Railsがなくても使えます。素のRubyプロジェクトやgem開発でも、Zeitwerk::Loaderを自分で生成すれば同じ命名規則で自動読み込みできます。各ライブラリが独自のローダー・inflector・ロガーを持てる設計です。


# Gemfile
gem "zeitwerk"

# lib/my_app.rb など起動ファイル
require "zeitwerk"

loader = Zeitwerk::Loader.new
loader.push_dir("#{__dir__}")
loader.setup          # ここでオートロード開始
# loader.eager_load   # 一括で読み込むなら

ディレクトリをpush_dirで登録し、setupを呼べば、以降は規約どおりのファイルが参照時に読み込まれます。CIや本番で全ファイルの整合性を確かめたいときはeager_loadを使います。注意点として、Zeitwerk 2.7系はRuby 3.2以上が必要です。古いRubyを使う環境では2.6系に固定するか、Rubyを上げる判断が要ります(対応バージョンの最新は公式で確認してください)。gem開発では、自作gemのロードにだけZeitwerkを使い、利用側には依存を強制しない構成が一般的です。

よくあるエラーと対処

Zeitwerkのトラブルは、ほとんどが「規約からのずれ」に起因します。頻出パターンと対処を整理します。

  • 定数未定義(NameError: uninitialized constant):ファイル名と定数名、またはディレクトリと名前空間が一致していない。app/services/payment_processor.rbPayment::Processorを置くと解決できず、app/services/payment/processor.rbへ分ける必要がある。
  • 頭字語が解決されないAPIHTTPClientのような綴りは既定の変換では通らない。inflectorに変換規則を登録する。
    
    Rails.autoloaders.main.inflector.inflect(
      "api" => "API",
      "http_client" => "HTTPClient"
    )
    
  • 特定ディレクトリを対象外にしたい:定数を持たないファイルや生成物はignoreで除外する。
    
    Rails.autoloaders.main.ignore("#{Rails.root}/app/deprecated")
    
  • require_dependencyが残っている:Classic時代の手動読み込みはZeitwerkでは不要。削除しても定数は解決される。循環参照がある場合は、定数の遅延参照へ設計を見直す。

よくある質問

Zeitwerkの読み方は?

ツァイトヴェルクと読みます。ドイツ語の Zeit(時間)と Werk(仕掛け・機構)を組み合わせた語です。Ruby/Railsのコードローダーを指し、同名の時計ブランドとは別物です。

Railsを使わず単体のgemとして使えますか?

使えます。Zeitwerk::Loaderを生成し、push_dirsetupを呼べば素のRubyプロジェクトやgem開発でも自動読み込みが機能します。

Classicモードはまだ使えますか?

使えません。ClassicモードはRails 7.0で削除され、config.autoloader=設定も無くなりました。現行のRails 8ではZeitwerkのみが選択肢です。

requireは書かなくてよいのですか?

アプリ内の自作クラス・モジュールは規約を守ればrequire不要です。ただしRuby標準ライブラリ(jsonなど)や外部gemは対象外なので、従来どおり明示的に読み込みます。

lib配下のクラスが読み込まれません

Rails 7.1以降はconfig.autoload_libで対象に加えます。さらに、lib内のファイルも命名規則と名前空間の一致が必要です。bin/rails zeitwerk:checkでずれを特定できます。

関連記事

資料請求

RELATED POSTS 関連記事