本書ではRailsの定数が自動読み込みおよび再読み込みされる仕組みについて説明します。
このガイドの内容:
autoload_paths
について、およびproduction環境でのeager loadingの仕組みrequire_dependency
についてRuby on Railsでコードを書き換えると、開発者がサーバーを再起動しなくても、あたかも既にアプリケーションに読み込み済みであるかのように動作します。
通常のRubyプログラムのクラスであれば、依存関係のあるプログラムを明示的に読み込む必要があります。
require 'application_controller' require 'post' class PostsController < ApplicationController def index @posts = Post.all end end
Rubyistとしての本能は、上のコードを見た瞬間に冗長な部分を目ざとく見つけることでしょう。クラスが、それが保存されているファイル名と同じ名前で定義されるのであれば、何とかしてそれらを自動的に読み込めないものでしょうか。依存するファイルを探索してその結果を保存しておけばよいのですが、こうした依存関係は不安定になりがちです。
さらに、Kernel#require
はファイルを一度しか読み込みませんが、読み込んだファイルが更新された時にサーバーを再起動せずに更新を反映できれば、開発はずっと楽になるでしょう。開発時にはKernel#load
を利用し、production環境ではKernel#require
を都合よく利用できれば便利です。
そしてまさに、Ruby on Railsでは以下のように書くだけでこのような便利な機能を利用できます。
class PostsController < ApplicationController def index @posts = Post.all end end
本章ではこの機能の仕組みについて解説します。
多くのプログラミング言語において、定数(constant)はさほど重要な位置を占めていませんが、ことRubyにおいては定数に関する話題が非常に豊富です。
Ruby言語の定数についての解説は本ガイドの範疇を超えますので深入りはしませんが、定数に関してはいくつかの重要な点にスポットライトを当てたいと思います。以降の章を十分に理解することは、Railsにおける定数の自動読み込みと再読み込みを理解するうえで頼もしい武器となることでしょう。
クラスおよびモジュールの定義をネストすることで、名前空間を作成できます。
module XML class SAXParser # (1) end end
ある場所におけるネスト(nesting)とは、ネストしたクラスオブジェクトやモジュールオブジェクトをネストの内側のものから順に並べたコレクションとなります(訳注: Ruby内のある場所のネストを調べるにはModule.nesting
を使えます)。前述の例における(1)の位置のネストは以下のようになります。
[XML::SAXParser, XML]
ここで重要なのは、ネストはクラスやモジュールの「オブジェクト」で構成されるという点です。ネストは、それにアクセスするための定数とも、ネストの名前とも関係ありません。
たとえば、以下の定義は前述の定義と似ています。
class XML::SAXParser # (2) end
(2)でネストを行った結果は異なります。
[XML::SAXParser]
単体のXML
はネストに含まれていません。
この例からわかるように、ある特定のネストに属するクラス名やモジュール名は、ネストの位置で表される名前空間と必ずしも相関していません。
さらに、両者は相関どころか互いに完全に独立しています。以下の例で考察してみましょう。
module X module Y end end module A module B end end module X::Y module A::B # (3) end end
(3)の位置で行われるネストは、以下のように2つのモジュールオブジェクトで構成されます。
[A::B, X::Y]
このネストの末尾はA
ではないどころか(そもそもこのA
はネストに属してすらいません)、ネストにX::Y
も含まれています。このX::Y
とA::B
は互いに独立しています。
このネストは、Rubyインタプリタによって維持されている内部スタックであり、以下のルールに従って変更されます。
class
キーワードに続けて記述されるクラスオブジェクトは、その内容が実行される時にスタックにプッシュされ、実行完了後にスタックからポップされる。
module
キーワードに続けて記述されるモジュールオブジェクトは、その内容が実行される時にスタックにプッシュされ、実行完了後にスタックからポップされる。
特異クラスはclass << object
でオープンされるときにスタックにプッシュされ、後でスタックからポップされる。
instance_eval
メソッドが文字列を1つ引数に取って呼び出されると、そのレシーバの特異クラスが、evalされたコードのネストにプッシュされる。class_eval
メソッドやmodule_eval
メソッドが文字列を1つ引数に取って呼び出されると、そのレシーバが、evalされたコードのネストにプッシュされる。
Kernel#load
によって解釈されるコードのトップレベルにあるネストは、空になる。ただしload
呼び出しが第2引数としてtrueという値を受け取る場合を除く。この値が指定されると、Rubyによって無名モジュールが新たに作成され、スタックにプッシュされる。
ここで興味深いのは、ブロックがスタックに何の影響も与えないという事実です。特に、Class.new
やModule.new
に渡される可能性のあるブロックは、new
メソッドによって定義されるクラスやモジュールをネストにプッシュしません。この点が、ブロックを使わずに何らかの形でクラスやモジュールを定義する場合と異なる点のひとつです。
以下のスニペットを実行するとクラスが (再オープンではなく) 新規作成されるとします。
class C end
RubyはObject
にC
という定数を作成し、その定数にクラスオブジェクトを保存します。このクラスインスタンスの名前は「C」という文字列であり、この定数の名前から付けられたものです。
すなわち、
class Project < ApplicationRecord end
上のコードは定数代入 (constant assignment) を行います。これは以下のコードと同等です。
Project = Class.new(ApplicationRecord)
このとき、クラスの名前は以下のように副作用として設定されます。
Project.name # => "Project"
この動作を実現するために、定数代入には特殊なルールが1つ設定されています。代入されるオブジェクトが無名クラスまたはモジュールである場合、Rubyはその定数の名前を用いてオブジェクトに名前を与えます。
無名クラスや無名モジュールにひとたび名前が与えられてしまえば、定数とインスタンスで何が行われても問題ではありません。たとえば、定数を削除することもできますし、クラスオブジェクトを別の定数に代入することも、どの定数にも保存しないでおくこともできます。ひとたび設定された名前はその後も変化しません。
以下のようにmodule
キーワードを指定してモジュールを作成する場合も、クラスの場合と同様に考えることができます。
module Admin end
上のコードは定数代入を行います。これは以下のコードと同等です。
Admin = Module.new
このとき、以下のようにモジュールの名前は副作用として設定されます。
Admin.name # => "Admin"
Class.new
やModule.new
に渡されるブロックの実行コンテキストは、class
およびmodule
キーワードを使う定義の本文の実行コンテキストと完全に同等とは限りません。しかし定数代入はどちらのイディオムを利用した場合にも同様に行われます。
俗に「String
クラス」と呼ばれているものを詳しく説明すると、実際は次のようになります。「String
クラス」とは、Object
定数に保存されたクラスオブジェクトに保存された「String」という定数に保存されたクラスオブジェクトのことです。それ以外の場合、String
はRubyのありふれた定数であり、解決アルゴリズムなどそれに関連するあらゆるものがこのString
という定数に適用されます。
コントローラについても同様に考えることができます。
class PostsController < ApplicationController def index @posts = Post.all end end
上のコードのPost
はクラスのための文法ではありません。そうではなく、Post
はRubyにおける通常の定数です。何も問題がなければ、この定数はall
メソッドを持つオブジェクトとして評価されます。
ここまで定数の自動読み込みについて詳しく解説したのはこのような理由からです。Railsは定数を必要に応じて読み込む機能を持っています。
Rubyの定数は、まったく文字通りの意味で「モジュールに属します」。クラスやモジュールは定数テーブルを1つ持ちます。これはハッシュテーブルのようなものと考えることができます。
その意味を十分理解するために、ひとつ例を示して分析してみましょう。「このString
クラス」のようなあいまいな表現は説明する側にとっては便利ですが、ここでは教育的な見地から厳密に説明することにします。
以下のモジュール定義について考察してみましょう。
module Colors RED = '0xff0000' end
最初にmodule
キーワードが処理されると、RubyインタプリタはObject
定数に保存されているクラスオブジェクトの定数テーブルに新しいエントリを1つ作成します。
このエントリは、「Colors」という名前と、新しく作られたモジュールオブジェクトを関連付けます。
さらに、Rubyインタプリタは新しいモジュールオブジェクトの名前を「Colors」という文字列として設定します。
後でこのモジュール定義の本体がRubyインタプリタによって解釈されると、Colors
定数の中に保存されたモジュールオブジェクトの定数テーブルの中に新しいエントリが1つ作成されます。このエントリは、「RED」という名前を「0xff0000」という文字列に対応付けます。
特にこのColors::RED
は、他のクラスオブジェクトやモジュールオブジェクトの中にあるかもしれない他のRED
定数とは、何の関連もないことにご注意ください。もし仮に他のRED
定数がたまたま存在するとしたら、それは独自の定数テーブルの中に異なるエントリとして存在するはずです。
特に前述の段落の説明を読む際には、クラスオブジェクト、モジュールオブジェクト、定数名、定数テーブルに関連付けられている値オブジェクトを混同しないよう、十分ご注意ください。
コードの任意の場所で、ネストが空でなければその最初の要素となり、空の場合にはObject
となるcrefを定義しましょう (訳注: crefはRuby内部におけるクラス参照 (class reference) の略であり、Rubyの定数が持つ暗黙のコンテキストです -- 関連記事 )。
ここでは詳しく述べませんが、相対的な定数参照を解決するアルゴリズムは以下のようになります。
ネストが存在する場合、この定数はそのネストの要素の中で順に探索される。それらの要素の先祖は探索されない (訳注: 本章で言う先祖 (ancestors) とはクラス、モジュールのスーパークラスとインクルードしているモジュールのことです)。
見つからない場合は、crefの先祖チェーン (継承チェーン) を探索する。
見つからない場合、crefでconst_missing
が呼び出される。const_missing
のデフォルトの実装はNameError
を発生するが、これはオーバーライド可能。
Railsの自動読み込みはこのアルゴリズムをエミュレートしているわけではないことにご注意ください。ただし探索の開始ポイントは、自動読み込みされる定数の名前と、cref自身です。詳しくは相対参照を参照してください。
修飾済み (qualified) 定数は以下のようなものです。
Billing::Invoice
Billing::Invoice
には2つの定数が含まれています。最初のBilling
は相対的な定数であり、直前のセクションで解説したアルゴリズムに基いて解決されます。
::Billing::Invoice
のように先頭にコロンを2つ置くと、最初のセグメントを相対から絶対に変えることができます。こうすることで、このBilling
はトップレベルの定数としてのみ参照されるようになります。
2番目のInvoice
定数はBilling
で修飾されています。この定数の解決方法についてはこの後で説明します。ここで、修飾する側のクラスやモジュールオブジェクト (上の例で言うBilling
) を親(parent)と定義します。修飾済み定数を解決するアルゴリズムは以下のようになります。
この定数はその親と先祖の中から探索される。Ruby 2.5以降では、先祖オブジェクトに挟まれているObject
はスキップされる。Kernel
やBasicObject
は従来どおりチェックされる。
探索の結果何も見つからない場合、親のconst_missing
が呼び出される。const_missing
のデフォルトの実装はNameError
を発生するが、これはオーバーライド可能。
Ruby 2.5より前のバージョンでは、String::Hash
はHash
と評価されてインタプリタが「toplevel constant Hash referenced by String::Hash」というwarningを出力します。Ruby 2.5以降ではObject
がスキップされるため、String::Hash
でNameError
がraiseされます。
ご覧のとおり、この探索アルゴリズムは相対定数の場合よりもシンプルです。特に、ネストが何の影響も与えていない点にご注意ください。また、モジュールは特別扱いされておらず、モジュール自身やモジュールの先祖のどちらにも定数がない場合にはObject
がチェックされない点にもご注意ください。
Railsの自動読み込みはこのアルゴリズムをエミュレートしているわけではないことにご注意ください。ただし探索の開始ポイントは、自動読み込みされる定数の名前と、その親です。詳細については修飾済み参照を参照してください。
定数パスで与えられた文字列を使って、親の名前空間(parent namespace)が定義されます。親の名前空間は、定数パスから最も右端のセグメントだけを除去した文字列になります。
たとえば、「A::B::C」という文字列の親の名前空間は「A::B」という文字列、「A::B」という文字列の親の名前空間は「A」という文字列、「A」という文字列の親の名前空間は「」という文字列になります。
しかし、クラスやモジュールについて考察する場合、親の名前空間の解釈にトリッキーな点が生じるので注意が必要です。例として、「A::B」という名前を持つモジュールMについて考察してみましょう。
親の名前空間「A」は、与えられた位置におけるネストの状態を反映していない可能性がある。
A
という定数は既に存在しない可能性がある。この定数は何らかのコードによってObject
から削除されているかもしれない。
たとえA
という定数が存在するとしても、かつてA
という名前を持っていたクラスまたはモジュールは既に存在していない可能性がある。たとえば、定数が1つ削除された後に別の定数代入 (constant assignment) が行われたとすると、一般的にはそれは別のオブジェクトを指していると考えるべき。
そのような状況でA
という同じ名前の定数が再度代入されると、そのA
は同じく「A」という名前を持つ別の新しいクラスまたはモジュールを指す可能性すらある。
上述のシナリオが発生した場合、MというモジュールA::B
という名前で参照できなくなってしまうが、Mというモジュールオブジェクト自身は「A::B」という名前のまま、削除されることもなくどこかに生きている可能性がある。
この「親の名前空間」は自動読み込みアルゴリズムの中核となるアイディアであり、アルゴリズム開発上の意図を直感的に説明するのに役立ちますが、このメタファーだけでは説明しきれない部分が多くあります。エッジケースでどのようなことが起きるかを十分理解するために、本章で説明されている「親の名前空間」という概念とその意味を正確に理解したうえで、親の名前空間を常に意識しながら読み進めてください。
config.cache_classes
がfalseに設定されていると、Kernel#load
による読み込みが行われます。これはdevelopmentモードにおけるデフォルトの設定です。他方、Kernel#require
を使う読み込みは、productionモードにおけるデフォルトの設定です。
定数の再読み込みが有効になっていると、Kernel#load
が使われ、ファイルを繰り返し実行できるようになります。
本ガイドでは「読み込み」(load)という言葉を、指定されたファイルがRailsによって解釈されるという程度の緩やかな意味で使っていますが、実際のメカニズムとしてはフラグに応じてKernel#load
やKernel#require
が使われます。
Railsは、そのための環境が設定されていれば常に自動読み込みを行います。たとえば、以下のrunner
コマンドを実行すると自動読み込みが行われます。
$ bin/rails runner 'p User.column_names' ["id", "email", "created_at", "updated_at"]
この場合、コンソール、テストスイート、アプリケーションのすべてで自動読み込みが行われます。
productionモードで起動された場合は、デフォルトでファイルのeager loading(事前一括読み込み)が行われるため、developmentモードのような自動読み込みはほぼ発生しません。ただし、自動読み込みはeager loadingでも発生することがあります。
以下の例で考察してみましょう。
class BeachHouse < House end
app/models/beach_house.rb
はeager loadingされているにもかかわらずHouse
が見つからない場合、Railsはこれについて自動読み込みを行います。
autoload_paths
とeager_load_paths
これについてはご存じの方も多いことでしょう。以下のようにrequire
で相対的なファイル名を指定したとします。
require 'erb'
このとき、Rubyは$LOAD_PATH
で指定されているディレクトリ内でこのファイルを探索します。具体的には、Rubyは指定されたすべてのディレクトリについて反復処理を行い、それらの中に「erb.rb」や「erb.so」や「erb.o」や「erb.dll」などの名前を持つファイルがあるかどうかを調べます。いずれかの名前を持つファイルがディレクトリで見つかれば、Rubyインタプリタはそのファイルを読み込み、探索をそこで終了します。見つからない場合はリストにある次のディレクトリで同じ処理を繰り返します。リストをすべて探索しても見つからない場合はLoadError
が発生します。
定数の自動読み込みについては後ほど詳しく説明しますが、その中核となるアイディアは次のとおりです。たとえばPost
のような定数が、コード中に出現した時点では未定義であったとします。このときapp/models
ディレクトリにpost.rb
というファイルがあれば、Railsはこの定数を探索・評価し、その結果Post
という定数を「副作用として」定義します。
ところで、Railsにはpost.rb
のようなファイルを探索する$LOAD_PATH
に似た、ディレクトリのコレクションがあります。このコレクションはautoload_paths
と呼ばれており、デフォルトで以下が含まれます。
起動時にアプリケーションやエンジンのapp
ディレクトリ以下に存在するすべてのサブディレクトリ(app/controllers
など)。app
以下に置かれるapp/workers
などのカスタムディレクトリは、すべてautoload_paths
に自動的に属するので、デフォルトのディレクトリである必要はない。
アプリケーションやエンジンのすべてのapp/*/concerns
第2サブディレクトリ。
test/mailers/previews
ディレクトリ。
eager_load_paths
は、初期段階では上のapp
パスになります。
通常、ファイルがeager_load
設定やcache_classes
設定に応じて自動読み込みされる方法は、developmentモード/productionモード/testモードでそれぞれ変わります。
developmentモード: アプリケーションのコードをインクリメンタルに読み込むことで起動を早くしたいので、eager_load
はfalse
にすべきです。これによってRailsはファイルを必要に応じて自動読み込みします(後述の自動読み込みのアルゴリズムを参照)。そしてファイルが変更されたときに再読み込みします(後述の定数の再読み込みを参照)。
productionモード: 起動に時間をかける代わりに一貫性とスレッド安全性を保ちたいので、eager_load
はtrue
になります。このときのRailsは、起動時(つまりアプリケーションがリクエストを受け付け可能になるまでの間)にeager_load_paths
にあるファイルをすべて読み込み、その後自動読み込みをオフにします(注: 自動読み込みはeager loading中に必要になることもあります)。起動がgood thing
になった後は、自動読み込みは行われません。自動読み込みはアプリケーションでスレッド安全性の問題を引き起こす可能性があるからです。
testモード: (個別のテストの)実行速度が欲しいので、eager_load
はfalse
になります。つまりRailsの振る舞いはdevelopmentモードと同じになります。
新しく生成されたRailsアプリケーションでは、上に示した挙動がデフォルトになります。この挙動はさまざまな方法で変更可能です (Railsアプリケーションを設定するを参照)。しかし過去バージョン(Rails 5より前)の独自のautoload_paths
を使う場合、開発者がautoload_paths
に別のディレクトリを追加していた可能性も考えられます(例: lib
はかつて自動読み込みのパスリストに含まれていましたが、現在は違います)。しかしこれはproductionモードでのみ発生するエラーの原因になる可能性があるため、今では残念な結果に終わることがほとんどです。config.eager_load_paths
やconfig.autoload_paths
のどちらにも新しいディレクトリを追加することは可能ですが、使う場合は自己責任でお願いします。
test環境での自動読み込みも参照してください。
config.autoload_paths
は環境固有の設定ファイルからは変更できません。
autoload_paths
の値を調べることもできます。生成したRailsアプリケーションでは以下のようになります (ただし編集してあります)。
$ bin/rails r 'puts ActiveSupport::Dependencies.autoload_paths' .../app/assets .../app/channels .../app/controllers .../app/controllers/concerns .../app/helpers .../app/jobs .../app/mailers .../app/models .../app/models/concerns .../activestorage/app/assets .../activestorage/app/controllers .../activestorage/app/javascript .../activestorage/app/jobs .../activestorage/app/models .../actioncable/app/assets .../actionview/app/assets .../test/mailers/previews
autoload_paths
は初期化中に算出され、キャッシュされます。ディレクトリ構造が変更された場合、変更を反映するにはアプリケーションを再起動する必要があります。
定数の相対的な参照は、以下のようなさまざまな場所で行われます。
class PostsController < ApplicationController def index @posts = Post.all end end
上のコードで使われている3つの定数はすべて相対参照です。
class
およびmodule
キーワードの後に置かれる定数Rubyは、class
やmodule
キーワードの後ろに置かれる定数を探索します。その目的は、それらのクラスやモジュールがその場所で初めて作成されるのか、再度オープンされるのかを確認することです。
その時点で定数が未定義である場合、Rubyは定数が「見つからない(missing)」とは見なさず、自動読み込みはトリガーされません。
前述の例で言うと、ファイルがRubyインタプリタによって解釈される時点でPostsController
が定義されていない場合、Railsの自動読み込みはトリガーされず、Rubyは単にコントローラを定義します。
逆に、ApplicationController
がそれまでに出現していなかった場合、この定数は「見つからない(missing)」と見なされ、Railsによって自動読み込みがトリガーされます。
Railsは、ApplicationController
を読み込むためにautoload_paths
にあるパスを順に処理します。最初にapp/assets/application_controller.rb
が存在するかどうかを確認します。見つからない場合 (これが通常です)、次のパスでapp/controllers/application_controller.rb
の探索を続行します。
見つかったファイルでApplicationController
が定義されていればOKです。定義されていない場合はLoadError
が発生します。
unable to autoload constant ApplicationController, expected <application_controller.rbへのフルパス> to define it (LoadError)
Railsでは、自動読み込みされた定数の値がクラスオブジェクトやモジュールオブジェクトである必要はありません。たとえば、app/models/max_clients.rb
というファイルでMAX_CLIENTS = 100
と定義されている場合、MAX_CLIENTS
の自動読み込みは問題なく行われます。
ApplicationController
の自動読み込みは、それが行われる箇所のネストが空であるため、autoload_paths
のディレクトリの下で直接行われているように見えます。Post
の状況はこれとは異なります。その行におけるネストは[PostsController]
であり、名前空間のサポートが効力を発揮し始めます。
基本的な考え方を以下に示します。
module Admin class BaseController < ApplicationController @@all_roles = Role.all end end
Role
を自動読み込みするにあたり、Role
が定義済みであるかどうかを現在の名前空間または親の名前空間でひとつずつチェックします。つまり、概念上は以下のいずれかを上から順に自動読み込みしたいのです。
Admin::BaseController::Role Admin::Role Role
そしてここが肝心です。これを行なうために、Railsはautoload_paths
のパスで以下のようなファイル名があるかどうかをそれぞれについて探索します。
admin/base_controller/role.rb admin/role.rb role.rb
探索対象となるその他の追加ディレクトリについては後述します。
'Constant::Name'.underscore
は、Constant::Name
が定義されていると期待されるファイルへの相対パスを返します。このファイル名は拡張子を含みません。
前述のPostsController
で、RailsがPost
定数をどのように自動読み込みするかを詳しく見てみましょう。このアプリケーションのapp/models/post.rb
にPost
モデルが定義されているとします。
最初に、autoload_paths
のパスの中にposts_controller/post.rb
があるかどうかをチェックします。
app/assets/posts_controller/post.rb app/controllers/posts_controller/post.rb app/helpers/posts_controller/post.rb ... test/mailers/previews/posts_controller/post.rb
この探索は失敗に終わるので、今度はディレクトリの有無を探索します。その理由については次のセクションで説明します。
app/assets/posts_controller/post app/controllers/posts_controller/post app/helpers/posts_controller/post ... test/mailers/previews/posts_controller/post
これらの探索がすべて失敗すると、Railsは親の名前空間で探索を続行します。この例の場合、親はトップレベルしかありません。
app/assets/post.rb app/controllers/post.rb app/helpers/post.rb app/mailers/post.rb app/models/post.rb
やっとマッチするファイルapp/models/post.rb
が見つかりました。探索はここで終了し、ファイルが読み込まれます。Post
がこのファイルで実際に定義されていればすべてOKです。定義されていない場合はLoadError
が発生します。
修飾済み (qualified) 定数が見つからない場合、この定数は親の名前空間では探索されません。しかしここで注意すべき点がひとつあります。定数が見つからない場合、そのトリガーが相対的な参照だったのか、修飾済み参照だったのかをRailsは区別できません。
以下の例について考察してみましょう。
module Admin User end
以下についても考察します。
Admin::User
User
が見つからない場合、Railsは上のどちらについても、「User」という定数が「Admin」というモジュールの中にはないということしか認識しません。
このUser
がトップレベルにある場合、1番目の例はRubyによって解決されますが、2番目の例は解決されません。Railsは、一般にRubyの定数解決アルゴリズムをエミュレートしませんが、この場合は以下のヒューリスティックを利用して解決を図ります。
見つからない定数が、そのクラスまたはモジュールの親の名前空間にも存在しない場合、Railsはこの定数を相対参照であると仮定する。そうでない場合は修飾済み参照であると仮定する。
たとえば、以下のコードが自動読み込みをトリガし、
Admin::User
かつObject
にUser
定数が既に存在する場合、以下のコードでは解決不能です。
module Admin User end
理由は、もしそうでなければRubyはUser
を解決できたはずであり、そもそも最初の位置で自動読み込みも行われなかったはずだからです。この場合Railsはこの定数が修飾済み参照であると仮定し、admin/user.rb
ファイルとadmin/user
ディレクトリが唯一の正当なオプションであるとみなします。
実用上は、そのネストがそれぞれの親名前空間にすべてマッチし、かつそのルールが適用される定数の存在がその時点で認識されている限り、この方法はおおむね機能します。
しかし、自動読み込みは要求に応じて発生するものです。その時点でたまたまトップレベルのUser
が読み込まれていなければ、Railsはこのヒューリスティックに従って定数を相対参照であると仮定します。
このような名前の競合は実際にはめったに発生しませんが、もし発生した場合は、require_dependency
が解決手段を提供します。require_dependency
を使うと、競合が発生する場所でこのヒューリスティックを発動する必要のある定数が定義されるようにできます。
あるモジュールがひとつの名前空間として振る舞う場合、Railsアプリケーションではそのモジュールのためのファイルを定義する必要はありません。その名前空間にマッチするディレクトリがあれば十分です。
あるRailsアプリケーションに管理機能があり、そのコントローラがapp/controllers/admin
に保存されているとします。このAdmin
モジュールが読み込まれていない状態でAdmin::UsersController
へのアクセスが発生する場合、Railsは最初にAdmin
という定数を自動読み込みしておく必要があります。
admin.rb
というファイルがautoload_paths
のパスに含まれている場合は、Railsによって読み込まれます。しかし、admin
というファイルではなくadmin
というディレクトリが見つかった場合は、Railsによって空のモジュールがひとつ作成され、Admin
定数にその場で代入されます。
相対参照が「見つからない(missing)」と報告される場所は、相対参照がヒットしたcrefです。修飾済み参照が「見つからない」と報告される場所は、修飾済み参照の親です(crefの定義については本章の相対定数を解決するアルゴリズムを、parentの定義については同じく修飾済み定数を解決するアルゴリズムを参照してください)。
定数C
を任意の状況で自動読み込みする手順を擬似言語で表現すると以下のようになります。
if「定数Cが見つからないクラスまたはモジュール」がObjectである let ns = '' else let M = 定数Cが見つからないクラスまたはモジュール if Mが無名である let ns = '' else let ns = M.name end end loop do # 正規のファイルを探索する for dir in autoload_paths if "#{dir}/#{ns.underscore}/c.rb"ファイルが存在する load/require "#{dir}/#{ns.underscore}/c.rb" if 定数Cが定義済みである return else raise LoadError end end end # 自動モジュールを探索する for dir in autoload_paths if "#{dir}/#{ns.underscore}/c"ディレクトリが存在する if nsが空文字列である let C = Module.new in Object and return else let C = Module.new in ns.constantize and return end end end if nsが空である # 定数が見つからないままトップレベルに到達 raise NameError else if Cが親の名前空間のどこかに存在する # 修飾済み定数のヒューリスティック raise NameError else # 親の名前空間で探索を再試行する let ns = nsの親の名前空間 and retry end end end
require_dependency
定数の自動読み込みは必要に応じて自動的に行なわれるので、利用の際に定義済みである定数もあれば自動読み込みをトリガーする定数もあり、その動作は一定ではありません。自動読み込みは実行パスに依存しますし、実行パスはアプリケーションの実行中に変わる可能性があります。
しかし、あるコードを実行するときに特定の定数を認識させたいことがあります。このような場合にはrequire_dependency
を使います。require_dependency
は、その時点での読み込みのメカニズムを利用してファイルを読み込む方法を提供し、そのファイルで定義されている定数を監視することで、あたかも自動読み込み済みであるかのように必要に応じて定数を再読み込みできるようにします。
require_dependency
が必要になることはめったにありませんが、自動読み込みとSTIや定数がトリガーされない場合でいくつかの実例を参照できます。
require_dependency
は自動読み込みと異なり、特定の定数がそのファイルで定義されていることを前提としません。ファイルパスと定数のパスは一致するはずですが、この動作を乱用するのはよくありません。
config.cache_classes
がfalseの場合、Railsは自動読み込み済みの定数を再読み込み可能になります。
たとえば、Railsのコンソールセッションを開いている状態で、いくつかのファイルがバックグラウンドで更新された場合、reload!
コマンドを使って定数を再読み込みできます。
> reload!
アプリケーションの実行中に、関連するロジックが変更されると、コードが再読み込みされます。これを実現するために、Railsでは以下のさまざまな要素を監視しています。
config/routes.rb
ロケール
autoload_paths
以下にあるRubyファイル
db/schema.rb
およびdb/structure.sql
ファイル
これらのいずれかが変更されると、ミドルウェアが変更を検出してコードを再読み込みします。
自動読み込みされた定数は、自動読み込みのインフラによって監視されます。再読み込みの具体的な実装では、Module#remove_const
メソッドを呼び出して関連するクラスやモジュールをいったんすべて削除します。これにより、そのコードが実行されるとそれらの定数が再び「不明」の状態になり、必要に応じてファイルが再読み込みされます。
この操作は「オール・オア・ナッシング」です。Railsのクラスやモジュールの間にはきわめて微妙な依存関係があるため、変更部分だけを再読み込みすることはありません。Railsは、変更を検出するたびにすべてをクリーンアップします。
Module#autoload
は定数の遅延読み込み機能を提供します。この機能はRubyの定数探索アルゴリズムや動的定数APIなどと完全に統合されています。この動作はかなり透過的です。
Rails内部では、ブートプロセス以後の作業を可能な限り遅延するためにこの機能が広く使われています。ただし、Railsの定数自動読み込みはModule#autoload
で実装されているわけではありません。
もしModule#autoload
ベースの実装にするのであれば、たとえばアプリケーションのツリーをすべてスキャンし、既存のファイル名と従来の定数名を関連付けるためにautoload
を呼び出すという方法になったでしょう。
Railsでこのような実装を避けているのには多くの理由があります。
たとえば、Module#autoload
はファイル読み込みにrequire
しか使えないため、再読み込みには利用できないでしょう。しかも、内部ではKernel#require
とは別のrequire
が使われています。
つまり、Module#autoload
ではファイルが削除された場合にその宣言を削除する方法が提供されません。定数をModule#remove_const
で削除すると、以後autoload
がトリガーされなくなってしまいます。さらに、Module#autoload
では修飾名がサポートされていないため、名前空間を持つファイルはアプリケーションのツリーをスキャン中に独自のautoload
呼び出しをインストールすべきであるにもかかわらず、その時点でそれらのファイルの定数参照の設定が間に合わない可能性があります。
自動読み込みをModule#autoload
ベースで実装できればよいのですが、上述のとおり現時点では不可能です。実際にはRailsの定数自動読み込みはModule#const_missing
ベースで実装されています。本ガイドで述べている独自の用法が使われている理由がこれです。
以下の2つについて考察してみましょう。
module Admin class UsersController < ApplicationController def index @users = User.all end end end
および
class Admin::UsersController < ApplicationController def index @users = User.all end end
RubyがUser
を解決するときに、1番目の例ではAdmin
をチェックしますが、2番目の例はネストに属していないのでAdmin
をチェックしません(ネストおよび解決アルゴリズムを参照)。
残念ながらRailsの自動読み込みは、この定数が見つからない箇所でネストが発生しているかどうかを認識しないので、通常のRubyと同じように振る舞うことができません。特にAdmin::User
はどちらの場合にも自動読み込みされます。
技術的には、class
キーワードやmodule
キーワードの修飾済み定数が自動読み込みで動作することもありますが、修飾済み定数よりも以下のように相対定数を使うことをおすすめします。
module Admin class UsersController < ApplicationController def index @users = User.all end end end
単一テーブル継承(STI: Single Table Inheritance)はActive Recordの機能のひとつであり、モデルの階層構造を1つのテーブルに保存できます。このようなモデルのAPIは階層構造を認識し、よく使われる要素がそこにカプセル化されます。たとえば以下のクラスがあるとします。
# app/models/polygon.rb class Polygon < ApplicationRecord end # app/models/triangle.rb class Triangle < Polygon end # app/models/rectangle.rb class Rectangle < Polygon end
Triangle.create
は三角形(triangle)を表す行をひとつ作成し、Rectangle.create
は四角形を表す行をひとつ作成します。id
が既存レコードのIDであれば、Polygon.find(id)
は正しい種類のオブジェクトを返します。
コレクションに対して実行されるメソッドは、この階層構造も認識します。たとえば、三角形と四角形はどちらも多角形(polygon)の一種となるため、Polygon.all
はテーブル内のすべてのレコードを返します。Active Recordが返す結果セットでは、結果ごとに対応するクラスのインスタンスを返すように配慮されています。
種類は必要に応じて自動読み込みされます。たとえば、Polygon.first
の結果が四角形(rectangle)であり、Rectangle
がその時点で読み込まれていなければ、Active RecordによってRectangle
が読み込まれ、そのレコードは正しくインスタンス化されます。
ここまでは何の問題もありません。しかし、ルートクラスに基づいたクエリではなく、何らかのサブクラスを使わなければならない場合には事情が異なってきます。
Polygon
を操作する場合、テーブル内のすべてのエントリはpolygonとして定義されているので、どの子孫についても特別な配慮は浮揚です。しかしPolygon
のサブクラスに対して操作を行う場合、Active Recordが探索しようとしている種類をそのサブクラスで列挙可能である必要があります。以下の例で考察してみましょう。
以下のように、取得する種類の制限をクエリに加えると、Rectangle.all
はrectangleだけを読み込みます。
SELECT "polygons".* FROM "polygons" WHERE "polygons"."type" IN ("Rectangle")
今度はRectangle
のサブクラスを導入してみましょう。
# app/models/square.rb class Square < Rectangle end
Rectangle.all
は四角形と正方形の両方を返すはずです。
SELECT "polygons".* FROM "polygons" WHERE "polygons"."type" IN ("Rectangle", "Square")
ただしここで注意して欲しい点があります。Active RecordはSquare
クラスの存在をどのような方法で認識しているのでしょうか。
app/models/square.rb
というファイルが存在し、Square
クラスがその中で定義されていたとしても、クラス内のコードがその時点で一度も利用されていない場合は、Rectangle.all
によって以下のクエリが発行されます。
SELECT "polygons".* FROM "polygons" WHERE "polygons"."type" IN ("Rectangle")
これはバグではありません。その時点でのRectangle
クラスの既知の子孫は、クエリにすべて含まれています。
コードの実行順序にかかわらず常に期待どおりに動作させる手段のひとつとして、中間のクラスが定義されているファイルの末尾で、以下のように直下のサブクラスを手動で読み込むという方法があります。
# app/models/rectangle.rb class Rectangle < Polygon end require_dependency 'square'
これはあらゆる中間クラス(ルートクラスでも末端のleafクラスでもないクラス)で発生する必要があります。ルートクラスはクエリを型で絞り込みませんが、ルートクラスがすべての子孫についての知識を持たなければならないということにはなりません。
require
自動読み込みされる定数を定義するファイルは、決してrequire
されるべきではありません。
require 'user' # これは絶対だめ class UsersController < ApplicationController ... end
これはdevelopmentモードで以下の2つの落とし穴の原因となる可能性があります。
このrequire
が実行されるより前にUser
が自動読み込みされると、$LOADED_FEATURES
がload
によって更新されないので、app/models/user.rb
が再度実行されてしまう。
このrequire
が最初に実行されると、RailsはUser
を自動読み込み済み定数としてマーキングしないので、app/models/user.rb
の変更が再読み込みされなくなる。
フローに従って、「常に」定数の自動読み込みをお使いください。自動読み込みとrequire
は決して併用してはいけません。やむを得ない事情から、ファイルに特定のファイルをどうしても読み込んでおきたい場合は、最後の手段として、require_dependency
を使って定数の自動読み込みと調和させてください。このオプションが実際に必要になることはめったにないはずです。
もちろん、自動読み込みされたファイルでrequire
を用いて通常のサードパーティのライブラリを読み込むのは問題ありません。Railsはサードパーティのライブラリの定数を区別できるので、これらは自動読み込みの対象としてマークされません。
config/initializers/set_auth_service.rb
で以下の代入を行った場合について考察してみましょう。
AUTH_SERVICE = if Rails.env.production? RealAuthService else MockedAuthService end
この設定の目的は、AUTH_SERVICE
で指定された環境に対応するクラスをRailsアプリケーションから使えるようにすることです。developmentモードでは、イニシャライザ実行時にMockedAuthService
が自動読み込みされます。ここで、アプリケーションでいくつかのリクエストを処理した後、実装に変更を加え、再度アプリケーションにアクセスしたとしましょう。驚いたことに、変更したはずの実装が反映されていません。これはどういうことなのでしょう。
前述のとおり、自動読み込みされた定数はRailsによって削除されますが、AUTH_SERVICE
には元のクラスオブジェクトが保存されています。このオブジェクトは最新の状態ではないので元の定数を使ってアクセスできないにもかかわらず、完全に機能します。
以下のコードはこの状況をまとめたものです。
class C def quack 'quack!' end end X = C Object.instance_eval { remove_const(:C) } X.new.quack # => quack! X.name # => C C # => uninitialized constant C (NameError)
こうした理由から、定数をRailsアプリケーションの初期化時に自動読み込みするのはよいアイデアとは言えません。
上のような場合には、以下のように動的なアクセスポイントを実装し、
# app/models/auth_service.rb class AuthService if Rails.env.production? def self.instance RealAuthService end else def self.instance MockedAuthService end end end
さらにアプリケーションでAuthService.instance
を代りに使うという方法があります。AuthService
は必要に応じて読み込まれ、自動読み込みとよく調和するでしょう。
require_dependency
とイニシャライザ前述のとおり、require_dependency
は自動読み込みと調和するようにファイルを読み込みます。しかし、このような呼び出しはイニシャライザ内では意味がないのが普通です。
イニシャライザ内でrequire_dependency
呼び出しを使えば、たとえば自動読み込みとSTIの問題を修正しようとしたときと同様に特定の定数を確実に事前読み込みできるのではと思う人がいるかもしれません。
この方法の問題は、developmentモードでは、関連する変更がファイルシステム上で生じていなかった場合に自動読み込みされた定数が完全にクリーンアップされてしまうという点です。イニシャライザでのこのような定数の完全削除はぜひとも避けたいところです。
自動読み込みが行われる箇所でrequire_dependency
を使う場合は、十分戦略を練っておく必要があります。
フライトシミュレータで考察してみましょう。このアプリケーションには以下のデフォルトのフライトモデルが1つあります。
# app/models/flight_model.rb class FlightModel end
これは、以下のようにそれぞれの飛行機で上書きできます。
# app/models/bell_x1/flight_model.rb module BellX1 class FlightModel < FlightModel end end # app/models/bell_x1/aircraft.rb module BellX1 class Aircraft def initialize @flight_model = FlightModel.new end end end
イニシャライザはBellX1::FlightModel
をひとつ作成しようとし、ネストにはBellX1
があります。一見問題はなさそうですが、ここでデフォルトのフライトモデルが読み込まれ、Bell-X1のフライトモデルが読み込まれていなかったとします。このとき、RubyインタプリタはトップレベルのFlightModel
を解決可能になるので、BellX1::FlightModel
の自動読み込みはトリガーされなくなります。
このコードの振る舞いは、実行パスの内容に依存します。
この種のあいまいさを解決するために、以下のような修飾済み定数がよく利用されます。
module BellX1 class Plane def flight_model @flight_model ||= BellX1::FlightModel.new end end end
以下のようにrequire_dependency
でも解決できます。
require_dependency 'bell_x1/flight_model' module BellX1 class Plane def flight_model @flight_model ||= FlightModel.new end end end
この現象はRuby 2.5より前のバージョンにしか該当しません。
以下の例について考察します。
# app/models/hotel.rb class Hotel end # app/models/image.rb class Image end # app/models/hotel/image.rb class Hotel class Image < Image end end
Hotel::Image
は実行パスに依存するので、Hotel::Image
という記法にはあいまいさが生じます。
前述のとおり、RubyはHotel
とその先祖の定数を探索します。app/models/image.rb
が読み込まれているがapp/models/hotel/image.rb
が読み込まれていない状況では、RubyはImage
をHotel
内ではなくObject
内で探索します。
$ bin/rails r 'Image; p Hotel::Image' 2>/dev/null Image # これはHotel::Imageではない!
Hotel::Image
を評価するコードは、(おそらくrequire_dependency
を使って) app/models/hotel/image.rb
を事前に読み込み済みの状態にしておく必要があります。
ただしこの方法を使うと、Rubyインタプリタが以下の警告を出力します。
warning: toplevel constant Image referenced by Hotel::Image
この驚くべき定数解決方法は、実はあらゆる就職済みクラスで観察できます。
2.1.5 :001 > String::Array (irb):1: warning: toplevel constant Array referenced by String::Array => Array
この現象を実際に観察するには、修飾に使う名前空間がクラスでなければなりません。Object
はモジュールの先祖ではないからです。
以下のクラス定義があるとします。
# app/models/hotel/services.rb module Hotel class Services end end # app/models/hotel/geo_location.rb module Hotel class GeoLocation class << self Services end end end
app/models/hotel/geo_location.rb
が読み込まれるより前にHotel::Services
が認識されていれば、Services
はRubyによって解決されます。理由は、Hotel::GeoLocation
の特異クラス(singleton class)がオープンされるとHotel
がネストに属するからです。
しかしHotel::Services
がその時点で「不明」の場合、RailsはHotel::Services
を自動読み込みできず、NameError
が発生します。
その理由は、自動読み込みはその特異クラスのためにトリガーされるからです。特異クラスは無名であり、Railsは前述のようなエッジケースではトップレベルの名前空間しかチェックしません。
この警告を簡単に解決する方法のひとつは、以下のように定数を修飾することです。
module Hotel class GeoLocation class << self Hotel::Services end end end
BasicObject
の中で自動読み込みするBasicObject
の直系の子孫は先祖にObject
がないので、トップレベルの定数を解決できません。
class C < BasicObject String # NameError: uninitialized constant C::String end
ここに自動読み込みが絡むとさらに複雑になります。以下について考察してみましょう。
class C < BasicObject def user User # 誤り end end
Railsはトップレベルの名前空間をチェックするので、自動読み込みされたUser
はuser
メソッドが「最初に」呼び出されるときには問題なく動作します。その時点でUser
定数が既知の場合、特にuser
を2度目に呼び出すと例外が発生します。
c = C.new c.user # 驚くべきことにUserで問題が生じない c.user # NameError: uninitialized constant C::User
これは、親の名前空間に既に定数が存在していることが検出されたためです(修飾済み参照を参照)。
純粋なRubyの場合と同様、BasicObject
の直系の子孫オブジェクトの中では以下のように常に絶対定数パスをお使いください。
class C < BasicObject ::String # 正しい def user ::User # 正しい end end
test
環境の自動読み込みを設定する場合、以下のさまざまな要素を考慮する必要が生じる可能性があります。
たとえば、production環境での何らかの問題を事前にキャッチするため、test環境をproduction環境と完全に同一のセットアップ(config.eager_load = true
、config.cache_classes = true
)で回すことが有用になったとしましょう(development環境とproduction環境が同一でなくなることと引き換えですが)。しかしこれでは、開発用マシンで個別のテストを実行するときの起動が遅くなってしまいます(しかもspring gemとの互換性までただちに失われます: 後述)。これを実現する方法のひとつは、その設定をCI(継続的インテグレーション)マシンでのみ用いることです(spring gemはCI用マシンで動かすべきではありません)。
開発用マシンの場合は、テストを高速化するためにあらゆる手法を利用できます(config.eager_load = false
にできれば理想です)。
新しいRailsアプリケーションに同梱されているSpringプリローダgemを使う場合は、開発時にconfig.eager_load = false
のままにしておくことが理想です。ハイブリッドな設定(config.eager_load = true
、config.cache_classes = true
、config.enable_dependency_loading = true
を同時に使う)の場合は、spring gemのissueを参照してください。ただし、開発時の設定をあれこれ変えるよりも、(おそらくCIテストの結果による)自動読み込み失敗の原因となる問題を解決する方が結果的にシンプルにできるかもしれません。
場合によっては、テストのセットアップでRails.application.eager_load!
を用いて明示的にeager loadingを行う必要があるかもしれません。これはマルチスレッドに絡むテストで必要になる可能性があります。
Railsガイドは GitHub の yasslab/railsguides.jp で管理・公開されております。本ガイドを読んで気になる文章や間違ったコードを見かけたら、気軽に Pull Request を出して頂けると嬉しいです。Pull Request の送り方については GitHub の README をご参照ください。
原著における間違いを見つけたら『Rails のドキュメントに貢献する』を参考にしながらぜひ Rails コミュニティに貢献してみてください 🛠💨✨
本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。
Railsガイド運営チーム (@RailsGuidesJP)
Railsガイドは下記の協賛企業から継続的な支援を受けています。支援・協賛にご興味あれば協賛プランからお問い合わせいただけると嬉しいです。