定数の自動読み込みと再読み込み本書ではRailsの定数が自動読み込みおよび再読み込みされる仕組みについて説明します。Railsでコードを書き換えると、開発者がサーバーを再起動しなくても、あたかも既にアプリケーションに読み込み済みであるかのように動作します。通常のRubyプログラムのクラスであれば、依存関係のあるプログラムを明示的に読み込む必要があります。endendRubyistとしての本能は、上のコードを見た瞬間にそこに冗長な部分があることを察知するでしょう。クラスが、それが保存されているファイル名と同じ名前で定義されるのであれば、どうにかしてそれらを自動的に読み込めないものでしょうか。依存するファイルを探索してその結果を保存しておけばよいのですが、こうした依存関係は何かと不安定です。Kernel#requireはファイルを一度しか読み込みませんが、読み込んだファイルが更新された時にサーバーを再起動せずに更新を反映できれば、開発はずっと楽になるでしょう。開発時にはKernel#loadを利用し、本番ではKernel#requireを都合よく利用できれば便利です。Railsでは以下のように書くだけでこのような便利な機能を利用することができます。endend本章ではこの機能の仕組みについて解説します。多くのプログラミング言語において、定数はさほど重要な位置を占めていませんが、ことRubyにおいては定数に関する話題が非常に豊富です。Ruby言語の定数についての解説は本ガイドの範疇を超えますので深入りはしませんが、定数に関してはいくつかの重要な点にスポットライトを当てたいと思います。以降の章を十分に理解することは、Railsにおける定数の自動読み込みと再読み込みを理解するための頼もしい武器となることでしょう。ネストクラスおよびモジュールの定義をネストすることで名前空間を作成できます。Ruby内のある場所のネストを調べるにはModule.nestingを使用します)。前述の例における(1)の位置のネストは以下のようになります。XML]ネストはクラスやモジュールの「オブジェクト」で構成されるという点を理解することが重要です。ネストはそれにアクセスするための定数とは何の関係もなく、ネストの名前とも関係ありません。以下の定義は前述の定義と似ています。(2)end(2)でネストを行った結果は異なります。[XML::SAXParser]単体のXMLはネストに含まれていません。この例からわかるように、ある特定のネストに属するクラス名やモジュール名は、ネストの位置における名前空間と必ずしも相関していません。両者は相関どころか互いに完全に独立しています。以下の例で考察してみましょう。endend(3)の位置で行われるネストは、以下のように2つのモジュールオブジェクトで構成されます。(そもそもこのAはネストに属してすらいません)、ネストにX::Yも含まれています。このX::YとA::Bは互いに独立しています。このネストは、Rubyインタプリタによって維持されている内部スタックであり、以下のルールに従って変更されます。classキーワードに続けて記述されるクラスオブジェクトは、その内容が実行される時にスタックにプッシュされ、実行完了後にスタックからポップされる。moduleキーワードに続けて記述されるモジュールオブジェクトは、その内容が実行される時にスタックにプッシュされ、実行完了後にスタックからポップされる。objectでオープンされるときにスタックにプッシュされ、後でスタックからポップされる。*_evalで終わるメソッドが文字列を1つ引数に取って呼び出されると、そのレシーバの特異クラスはevalされたコードのネストにプッシュされる。Kernel#loadによって解釈されるコードのトップレベルにあるネストは空になる。load呼び出しが第2引数としてtrueという値を受け取る場合を除く。この値が指定されると、無名モジュールが新たに作成されてRubyによってスタックにプッシュされる。ここで興味深いのは、ブロックがスタックに何の影響も与えないという事実です。Class.newやModule.newに渡される可能性のあるブロックは、newメソッドによって定義されるクラスやモジュールをネストにプッシュしません。この点が、ブロックを使用せずに何らかの形でクラスやモジュールを定義する場合と異なる点の一つです。新規作成されるとします。CendRubyはObjectにCという定数を作成し、その定数にクラスオブジェクトを保存します。このクラスインスタンスの名前は"C"という文字列であり、この定数の名前から付けられたものです。これは以下のコードと同等です。Class.new(ActiveRecord::Base)このとき、以下のようにクラスの名前は副作用として設定されます。"Project"この動作を実現するために、定数代入には1つの特殊なルールが設定されています。代入されるオブジェクトが無名のクラスまたはモジュールである場合、Rubyはそれらのオブジェクトの名前をその定数の名前から引用して命名します。無名クラスや無名モジュールにひとたび名前が与えられた後は、定数とインスタンスで何が起きるかは問題ではありません。定数を削除することもできますし、クラスオブジェクトを別の定数に代入したりどの定数にも保存しないでおくこともできます。名前はいったん設定された後は変化しなくなります。moduleキーワードを指定して以下のようにモジュールを作成する場合も、クラスの場合と同様に考えることができます。Adminend上のコードは定数代入を行います。これは以下のコードと同等です。Module.newこのとき、以下のようにモジュールの名前は副作用として設定されます。"Admin"Class.newやModule.newに渡されるブロックの実行コンテキストは、classおよびmoduleキーワードを使用する定義の本文の実行コンテキストと完全に同等ではないことがあります。定数代入はどちらのイディオムを使用した場合にも同様に行われます。呼ばれる場合、その本当の意味はこういうことです。Objectという定数があり、その中にクラスオブジェクトが保存されていて、さらにその中に"String"と呼ばれる定数があり、さらにその中に保存されているクラスオブジェクトのことなのです。なければ、StringはRubyのありふれた定数であり、解決アルゴリズムなどそれに関連するあらゆるものがこのStringという定数に適用されます。コントローラについても同様に考えることができます。endend上のコードのPostはクラスのための文法ではありません。なく、PostはRubyにおける通常の定数です。何も問題がなければ、この定数はallメソッドをもつオブジェクトとして評価されます。ここまで定数の自動読み込みについて詳細に解説したのはこのような理由からです。Railsは定数を必要に応じて読み込む機能を持っています。定数はモジュールに保存されるRubyの定数は、まったく文字通りに「モジュールに属します」。クラスとモジュールには定数テーブルがあります。これはハッシュテーブルのようなものと考えることができます。これがどういうことなのかを十分理解するために、ひとつ例を示して分析してみましょう。"このStringクラス"のようなあいまいな表現は説明する側にとっては便利ですが、ここでは教育的な見地から厳密に説明することにします。以下のモジュール定義について考察してみましょう。'0xff0000'end最初にmoduleキーワードが処理されると、RubyインタプリタはObject定数に保存されているクラスオブジェクトの定数テーブルに新しいエントリを1つ作成します。このエントリは、"Colors"という名前と、新しく作られたモジュールオブジェクトを関連付けます。Rubyインタプリタは新しいモジュールオブジェクトの名前を"Colors"という文字列として設定します。後に、このモジュール定義の本体がRubyインタプリタによって解釈されると、Colors定数の中に保存されたモジュールオブジェクトの定数テーブルの中に新しいエントリが1つ作成されます。このエントリは、"RED"という名前を"0xff0000"という文字列に対応付けます。このColors::REDは、他のクラスオブジェクトやモジュールオブジェクトの中にあるかもしれない他のRED定数とは何の関連もないことにご注意ください。仮に他のRED定数がたまたま存在しているとしたら、それは独自の定数テーブルの中に異なるエントリとして存在するはずです。前述の段落の説明を読む際には、クラスオブジェクト、モジュールオブジェクト、定数名、定数テーブルに関連付けられている値オブジェクトをそれぞれ混同しないように十分注意してください。詳細についてはここでは述べませんが、相対的な定数参照を解決するアルゴリズムは以下のようになります。ネストが存在する場合、この定数はそのネストの要素の中で順に探索される。クラス、モジュールのスーパークラスとインクルードしているモジュールのことです)。見つからない場合、crefに対してconst_missingが呼び出される。const_missingのデフォルトの実装はNameErrorを発生するが、これはオーバーライド可能。Railsの自動読み込みはこのアルゴリズムをエミュレートしているわけではないことにご注意ください。探索の開始ポイントは、自動読み込みされる定数の名前と、cref自身です。詳細については相対参照を参照してください。定数は以下のようなものです。Billing::InvoiceBilling::Invoiceには2つの定数が含まれています。最初のBillingの方は相対的な定数であり、直前のセクションで解説したアルゴリズムに基いて解決されます。::Billing::Invoiceのように先頭にコロンを2つ置くことで、最初のセグメントを相対から絶対に変えることができます。このようにすると、このBillingはトップレベルの定数としてのみ参照されるようになります。2番目のInvoice定数の方はBillingで修飾されています。この定数の解決方法についてはこの後で説明します。親(parent)と定義します。修飾済み定数を解決するアルゴリズムは以下のようになります。この定数はその親と先祖の中から探索される。探索の結果何も見つからない場合、親のconst_missingが呼び出される。const_missingのデフォルトの実装はNameErrorを発生するが、これはオーバーライド可能。見てのとおり、この探索アルゴリズムは相対定数の場合よりもシンプルです。ネストが何の影響も与えていない点にご注意ください。モジュールは特別扱いされておらず、モジュール自身またはモジュールの先祖のどちらにも定数がない場合にはObjectはチェックされない点にもご注意ください。Railsの自動読み込みはこのアルゴリズムをエミュレートしているわけではないことにご注意ください。探索の開始ポイントは、自動読み込みされる定数の名前と、その親です。詳細については修飾済み参照を参照してください。親の名前空間は、定数パスからその右端のセグメントのみを除去した文字列になります。クラスやモジュールについて考察する場合、親の名前空間の解釈にトリッキーな点が生じるので注意が必要です。例として、"A::B"という名前を持つモジュールMについて考察してみましょう。与えられた位置におけるネストの状態を反映していない可能性がある。Aという定数は既に存在しなくなっている可能性がある。この定数は何らかのコードによってObjectから削除されているかもしれない。Aという定数が存在するとしても、かつてAという名前を持っていたクラスまたはモジュールは既に存在しなくなっている可能性がある。行われたとすると、一般的にはそれは別のオブジェクトを指していると考えるべき。そのような状況でAという同じ名前の定数が再度代入されると、そのAは同じく"A"という名前を持つ別の新しいクラスまたはモジュールを指す可能性すらありうる。上述のシナリオが発生した場合、A::Bという名前でMというモジュールを参照することはできなくなってしまうが、Mというモジュールオブジェクト自身は"A::B"という名前を持ったまま、削除されることもなくどこかに生きている可能性がある。この「親の名前空間」は自動読み込みアルゴリズムの中核となるアイディアであり、アルゴリズム開発上の意図を直感的に説明するのに役立ちますが、このメタファーだけでは説明しきれない部分が多くあります。エッジケースでどのようなことが起きるかを十分理解するために、本章で説明されている「親の名前空間」という概念とその意味を正確に理解したうえで、親の名前空間を常に意識しながら読み進めてください。読み込みのメカニズムconfig.cache_classesがfalseに設定されていると、Kernel#loadを使用して読み込みが行われます。これはdevelopmentモードにおけるデフォルトの設定です。他方、Kernel#requireを使用する読み込みは、productionモードにおけるデフォルトの設定です。定数の再読み込みが有効になっていると、Kernel#loadが使用され、ファイルを繰り返し読み込めるようになります。本章では「読み込み」(load)という言葉を、指定されたファイルがRailsによって解釈されるという程度の緩やかな意味で使用していますが、実際のメカニズムとしてはフラグに応じてKernel#loadやKernel#requireが使用されます。自動読み込みが可能となる状況Railsは、そのための環境が設定されていれば常に自動読み込みを行います。以下のrunnerコマンドを実行すると自動読み込みが行われます。"updated_at"]この場合、コンソール、テストスイート、アプリケーションのすべてについて自動読み込みが行われます。行われるため、developmentモードのような自動読み込みはほとんど発生しません。事前一括読み込みにおいても自動読み込みが発生することがあります。以下の例で考察してみましょう。Houseendapp/models/beach_house.rbは事前一括読み込みされているにもかかわらずHouseが見つからなかった場合、Railsはこれについて自動読み込みを行います。autoload_pathsこれについてはご存じの方も多いことでしょう。以下のように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ディレクトリ。このコレクションはconfig.autoload_pathsで設定できます。libディレクトリは以前はコレクションに含まれていましたが、現在は含まれていません。(オプトイン)、libディレクトリをautoload_pathsに追加することもできます。"#{Rails.root}/lib"autoload_pathsの値を検査することもできます。ActiveSupport::Dependencies.autoload_paths'.../app/assets.../app/controllers.../app/helpers.../app/mailers.../app/models.../app/controllers/concerns.../app/models/concerns.../test/mailers/previewsautoload_pathsは初期化中に算出され、キャッシュされます。ディレクトリ構造が少しでも変更された場合、変更を反映するにはアプリケーションを再起動する必要があります。定数の相対的な参照は、以下のようにさまざまな場所で行われます。endend上のコードで使用されている3つの定数はすべて相対参照されています。classおよびmoduleキーワードの後に置かれる定数Rubyはclassやmoduleキーワードの後ろに置かれる定数を探索します。その目的は、それらのクラスやモジュールがその場所で初めて作成されるのか、再度オープンされるのかを確認することです。その時点で定数が未定義である場合、Rubyは定数が見つからないとは見なさないので、自動読み込みはトリガーされません。前述の例で言うと、ファイルがRubyインタプリタによって解釈される時点でPostsControllerが定義されていない場合、Railsの自動読み込みはトリガーされず、Rubyは単にコントローラを定義します。逆に、ApplicationControllerがそれまでに出現していなかった場合、この定数は「見つからない」ものと見なされ、Railsによって自動読み込みがトリガーされます。Railsは、ApplicationControllerを読み込むためにautoload_pathsにあるパスを順に処理します。最初にapp/assets/application_controller.rbが存在するかどうかを確認します。(これが通常です)、次のパスでapp/controllers/application_controller.rbの探索を続行します。見つかったファイルでApplicationControllerが定義されていればOKです。定義されていない場合はLoadErrorが発生します。(LoadError)Railsでは、自動読み込みされた定数の値がクラスオブジェクトやモジュールオブジェクトである必要はありません。100と定義されている場合、MAX_CLIENTSの自動読み込みは問題なく行われます。名前空間ApplicationControllerの自動読み込みは、それが行われる箇所のネストが空であるため、autoload_pathsのディレクトリの下で直接行われているように見えます。Postの状況はこれとは異なります。その行におけるネストは[PostsController]であり、名前空間のサポートが効力を発揮し始めます。基本的な考え方を以下に示します。endendRoleを自動読み込みするにあたり、Roleが定義済みであるかどうかを自身あるいは親の名前空間でひとつずつチェックします。概念上は自動読み込みのいずれかを以下の順で行いたいわけです。Admin::BaseController::RoleAdmin::RoleRoleここが肝心です。これを行なうために、Railsはautoload_pathsのパスをファイル名順で以下のように探索します。admin/base_controller/role.rbadmin/role.rbrole.rb探索対象となるその他の標準的なディレクトリについては後述します。'Constant::Name'.underscoreは、Constant::Nameが定義されていると期待されているファイルへの相対パスを返します。このファイル名には拡張子は含まれません。RailsがPost定数をどのようにして前述のPostsControllerで自動読み込みするのかを詳しく見てみましょう。このアプリケーションにはapp/models/post.rbにPostモデルがあるとします。最初に、autoload_pathsのパスの中にposts_controller/post.rbが見つかるかどうかをチェックします。app/assets/posts_controller/post.rbapp/controllers/posts_controller/post.rbapp/helpers/posts_controller/post.rb...test/mailers/previews/posts_controller/post.rbこの探索は失敗に終わるので、今度はディレクトリの有無を探索します。その理由については次のセクションで説明します。app/assets/posts_controller/postapp/controllers/posts_controller/postapp/helpers/posts_controller/post...test/mailers/previews/posts_controller/postこれらの探索がすべて失敗すると、Railsは親の名前空間で探索を続行します。この例の場合、親はトップレベルしかありません。app/assets/post.rbapp/controllers/post.rbapp/helpers/post.rbapp/mailers/post.rbapp/models/post.rbやっとマッチするファイルapp/models/post.rbが見つかりました。探索はここで終了し、ファイルが読み込まれます。Postがこのファイルで実際に定義されていればすべてOKです。定義されていない場合はLoadErrorが発生します。定数が見つからない場合、この定数は親の名前空間では探索されません。ここで注意すべき点がひとつあります。定数が見つからない場合、Railsはそのトリガーが相対的な参照であるか、修飾済み参照であるかを判定できなくなります。以下の例について考察してみましょう。Userend以下についても考察します。いうモジュールの中にはないということだけを認識します。このUserがトップレベルにあるとすると、1番目の例はRubyによって解決されますが2番目の例は解決されません。Railsは、一般にRubyの定数解決アルゴリズムをエミュレートしませんが、このような場合には以下のヒューリスティックを利用して解決を図ります。見つからない定数がそのクラスまたはモジュールの親の名前空間にもない場合、Railsはこの定数を相対参照であると仮定する。ない場合は修飾済み参照であると仮定する。以下のコードが自動読み込みを行い、Admin::UserかつUser定数がObjectに既にある場合、以下のコードでは同じ状況になりません。UserendそうでなければRubyはUserを解決でき、そもそも最初の位置で自動読み込みは行われないはずです。この場合Railsはこの定数が修飾済み参照であると仮定し、admin/user.rbファイルとadmin/userディレクトリが唯一の正当なオプションであるとみなします。そのネストがすべての親名前空間にそれぞれマッチし、かつそのルールを適用させる定数の存在がその時点でRubyに認識されていれば、この方法は実用上機能します。自動読み込みは要求に応じて発生するものです。その時点でたまたまトップレベルのUserが読み込まれていなければ、Railsはこのヒューリスティックに従って定数を相対参照であると仮定します。このような名前の競合は実際にはめったに発生しませんが、もし発生した場合は、require_dependencyを使用して、競合の発生する場所でこのヒューリスティックを発動する必要のある定数が定義されるようにできます。自動モジュールモジュールがひとつの名前空間のように振る舞う場合、Railsアプリケーションではそのモジュールのためのファイルを定義する必要はありません。その名前空間にマッチするディレクトリがあれば十分です。あるRailsアプリケーションに管理機能があり、そのためのコントローラがapp/controllers/adminに保存されているとします。このAdminモジュールが読み込まれていない状態でAdmin::UsersControllerへのアクセスが発生する場合は、Railsは最初にAdminという定数を自動読み込みしておく必要があります。admin.rbというファイルがautoload_pathsのパスに含まれている場合は、Railsによって読み込まれます。そのようなファイルが見つからず、adminというディレクトリが見つかった場合は、Railsによって空のモジュールがひとつ作成され、Admin定数にその場で代入されます。相対参照は、それらがヒットしたcrefからは見つからないと報告されます。修飾済み参照はその親からは見つからないと報告されます。(crefの定義については本章の相対定数を解決するアルゴリズムを、parentの定義については同じく修飾済み定数を解決するアルゴリズムを参照してください)定数Cを任意の状況で自動読み込みする手順を擬似言語で表現すると以下のようになります。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のクラスやモジュールの間にはきわめて微妙な依存関係があるため、変更が生じたクラスやモジュールだけを部分的に再読み込みすることはありません。クラスやモジュールは、変更が検出されるたびにすべてクリーンアップされます。Module#autoloadが関与しない場合Module#autoloadは定数の遅延読み込み機能を提供します。この機能はRubyの定数探索アルゴリズムや動的定数APIなどと完全に統合されています。この動作はきわめて透過的です。Rails内部では、ブートプロセス以後の作業を可能な限り遅延するためにこの機能が広く使用されています。Railsの定数自動読み込みではModule#autoloadを使用して実装されているわけではありません。実装でModule#autoloadを使用するのであれば、たとえばアプリケーションのツリーをすべてスキャンし、既存のファイル名と従来の定数名を関連付けるためにautoloadを呼び出すという方法が考えられます。Railsでこのような実装を避けているのにはいくつかの理由があります。Module#autoloadはrequireを使用するファイル読み込みしか行えないため、再読み込みには利用できないことがあります。このモジュールの内部ではKernel#requireとは別のrequireが使用されています。そのため、このモジュールではファイルが削除された場合にその宣言を削除する方法が提供されていません。定数をModule#remove_constで削除すると、以後autoloadがトリガーされなくなってしまいます。このモジュールでは修飾名がサポートされていません。アプリケーションのツリーをスキャンして個別のautoload呼び出しをインストールする際に名前空間を解釈する必要がありますが、それらのファイルの定数参照がその時点でまだ構成されていない可能性があります。Module#autoloadを使用して素直に自動読み込みを実装できればよいのですが、上述のとおり現時点では不可能です。実際にはRailsの定数自動読み込みはModule#const_missingを使用して実装されています。そのような独自の用法がある理由は本章で述べているとおりです。以下の2つについて考察してみましょう。endendRubyはUserを解決するために、1番目の例ではAdminをチェックしますが、2番目の例はネストに属していないのでAdminをチェックしません。(ネストおよび解決アルゴリズムを参照)残念ながら、Railsの自動読み込みはこの定数が見つからない箇所でネストが発生しているかどうかを認識しないので、通常のRubyと同じように振る舞うことができません。Admin::Userはどちらの場合にも自動読み込みされます。classキーワードやmoduleキーワードの修飾済み定数は自動読み込みのいくつかのケースで動作することもありますが、修飾済み定数よりも以下のように相対定数を使用することをお勧めします。Recordの機能のひとつであり、モデルの階層構造を1つのテーブルに保存することができます。このようなモデルのAPIは階層構造を認識し、よく使われる要素がそこにカプセル化されます。以下のクラスがあるとします。PolygonendTriangle.createは三角形を表す行をひとつ作成し、Rectangle.createは四角形を表す行をひとつ作成します。idが既存レコードのIDであれば、Polygon.find(id)は正しい種類のオブジェクトを返します。コレクションに対して実行されるメソッドは、階層構造も認識します。含まれるため、Polygon.allはテーブル内のすべてのレコードを返します。Recordが返す結果セットでは、結果ごとに対応するクラスのインスタンスを返すように配慮されています。種類は必要に応じて自動読み込みされます。RecordによってRectangleが読み込まれ、そのレコードは正しくインスタンス化されます。ここまでは何の問題もありません。ルートクラスに基づいたクエリではなく、何らかのサブクラスを使用しなければならない場合には事情が異なってきます。Polygonに対して操作を行なうのであれば、テーブル内のすべてのエントリはpolygonとして定義されているので、どの子孫についても特別な配慮は必要はありません。Recordが探索しようとしている種類をそのサブクラスで列挙可能である必要があります。以下の例で考察してみましょう。以下のように、取得する種類の制限をクエリに加えると、Rectangle.allはrectangleだけを読み込みます。("Rectangle")今度はRectangleのサブクラスを導入してみましょう。RectangleendRectangle.allは四角形と正方形の両方を返します。"Square")ただしここで注意が必要です。RecordはSquareクラスの存在をいったいどのようにして認識しているのでしょうか。app/models/square.rbというファイルが存在してSquareクラスがその中で定義されていたとしても、クラス内のコードがその時点で利用されたことがなかった場合はRectangle.allによって以下のクエリが発行されます。("Rectangle")これはバグではありません。Rectangleクラスのその時点で既知である子孫はクエリにすべて含まれています。明示的に読み込むという方法があります。‘square’この方法で明示的に読み込む必要があるのは、孫またはそれ以下のleafだけで十分です。直下のサブクラスである子については事前読み込みは不要です。階層構造が子よりも深い場合、階層の途中にあるクラスの定義で定数がスーパークラスとして記述されているので、途中のクラスは再帰的に自動読み込みされます。自動読み込みとrequire自動読み込みされる定数を定義するファイルはrequireすべきではありません。...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で以下の代入を行った場合について考察してみましょう。MockedAuthServiceendこの設定の目的は、AUTH_SERVICEで示される環境に対応するクラスをRailsアプリケーションから使用できるようにすることです。developmentモードでは、イニシャライザ実行時にMockedAuthServiceが自動読み込みされます。ここで、アプリケーションでいくつかのリクエストを処理した後、実装に変更を加え、再度アプリケーションにアクセスしたとしましょう。驚くことに、変更したはずの実装が反映されていません。これはどういうことなのでしょう。前述のとおり、自動読み込みされた定数はRailsによって削除されるのですが、AUTH_SERVICEには元のクラスオブジェクトが保存されています。このオブジェクトは最新の状態ではなくなっており、元の定数を使用してアクセスすることはできなくなっていますが、完全に機能します。以下のコードはこの状況をまとめたものです。(NameError)こうした理由から、Railsアプリケーションの初期化時に定数を自動読み込みするのはよいアイディアとは言えません。endendさらにアプリケーションでAuthService.instanceを代りに使用するという方法があります。AuthServiceは必要に応じて読み込まれ、自動読み込みとよく調和します。require_dependencyとイニシャライザ前述のとおり、require_dependencyは自動読み込みと調和するようにファイルを読み込みます。このような呼び出しはイニシャライザ内では意味がないのが普通です。イニシャライザ内でrequire_dependency呼び出しを使用することで、たとえば自動読み込みとSTIの問題を修正しようとしたときと同様に特定の定数を確実に事前読み込みできます。この方法の問題は、developmentモードでは、関連する変更がファイルシステム上で生じていなかった場合に自動読み込みされた定数が完全にクリーンアップされてしまうという点です。イニシャライザでのこのような定数の完全削除はぜひとも避けたいところです。自動読み込みが行われる箇所でrequire_dependencyを使用する場合は、十分戦略を練っておく必要があります。相対参照フライトシミュレータについて考察してみましょう。このアプリケーションには以下のデフォルトのフライトモデルが1つあります。FlightModelendこれは、以下のようにそれぞれの飛行機で上書きすることができます。endendイニシャライザはBellX1::FlightModelをひとつ作成しようとし、ネストにはBellX1があります。一見したところ問題はなさそうにみえます。ここで、デフォルトのフライトモデルが読み込まれ、Bell-X1のフライトモデルが読み込まれていなかったとします。このとき、RubyインタプリタはトップレベルのFlightModelを解決できるので、BellX1::FlightModelの自動読み込みはトリガーされません。このコードの振る舞いは、実行パスの内容に依存します。この種のあいまいさの解決には、以下のような修飾済み定数が役に立つことがしばしばあります。endend以下のようにrequire_dependencyを使用して解決することもできます。以下の例について考察します。endendHotel::Imageは実行パスに依存するので、この記法にはあいまいさが生じます。前述のとおり、RubyはHotelとその先祖の定数を探索します。app/models/image.rbが読み込まれているがapp/models/hotel/image.rbが読み込まれていない状況になった場合、RubyはImageをHotel内ではなくObject内で探索します。app/models/hotel/image.rbを事前に読み込み済みの状態にしておく必要があります。この方法を使用する場合、Rubyインタプリタは以下のような警告を出力します。Hotel::Imageこの驚くべき定数解決方法は、実はどのクラスを修飾するときにも観察できます。Arrayこの落とし穴を実際に観察するのであれば、名前空間の修飾はクラスである必要があります。Objectはモジュールの先祖ではないからです。特異クラス内で自動読み込みを行う以下のクラス定義があるとしましょう。endendapp/models/hotel/geo_location.rbが読み込まれているときより前にHotel::Servicesが認識されていれば、ServicesはRubyによって解決されます。これは、Hotel::GeoLocationの特異クラスがオープンされているとHotelがネストに属するからです。Hotel::Servicesがその時点で認識されていなければ、RailsはHotel::Servicesを自動読み込みできず、NameErrorが発生します。その理由は、自動読み込みは特異クラスのためにトリガーされるからです。特異クラスは無名であり、Railsは前述のような極端な状況においてはトップレベルの名前空間しかチェックしません。この警告を解決する簡単な方法のひとつに、定数を修飾するという方法があります。BasicObjectの中で自動読み込みするBasicObjectの直系の子孫については、その先祖にObjectがないためトップレベルの定数を解決できません。C::Stringendここに自動読み込みが絡んでくると事態が複雑になります。以下について考察してみましょう。endendRailsはトップレベルの名前空間をチェックするので、自動読み込みされたUserはuserメソッドが「最初に」呼び出されるときには問題なく動作します。その時点でUser定数が既知の場合、特にuserを2度目に呼び出した場合は例外が発生します。C::Userこれは、親の名前空間に既に定数が存在していることが検出されたためです。(修飾済み参照を参照)純粋なRubyの場合と同様、BasicObjectの直系の子孫オブジェクトの中では常に絶対定数パスを使用してください。Railsガイドを、もっと便利に。本サービスで得られた売上はRailsガイドを継続的に更新・運営するために活用させていただきます。管理・公開されております。出して頂けると嬉しいです。:)本ガイドの品質向上に向けて、皆さまのご協力が得られれば嬉しいです。株式会社https://yasslab.jp/支援・協賛Railsガイドは下記のサポーターから継続的な支援を受けています。までお問い合わせください。ライセンスに基づいて公開されています。よる登録商標で、すべての権利を有しています。