Railsの開発環境の自動再読み込みはどうやってるのか
以前から気になっていたけど調べたことのなかったRailsのアノ機能を調べてみました。開発環境でソースを修正→ブラウザーをリロード→変更が反映、の機能です。
どうやっているかというと、RubyのModule#const_missingとModule#remove_constを使っています。ソースコードはactivesupport-x.x.x/lib/active_support/dependencies.rbにあります。
- Module#const_missingを定義する。
- 存在しないクラス名(定数名)にぶつかると、const_missingが呼ばれる。
- const_missingでは、クラス名をファイル名に変換し、ディレクトリ(app/controllers、app/models、libなど)を調べて、ファイルがあればrequireまたはloadする。
- 新しいクラス名が定義されれば、名前を保存しておく。
- リロードされたら(HTTPリクエストが来たら)、保存したクラス名(定数名)をすべてModule#remove_constで削除する。
- 1-1.に戻る。
ソースファイルのタイムスタンプを見ているかと思ってましたが、そこまではしていないようです。
リロードするたびにクラスオブジェクトがメモリに溜まっていきそうな感じですが、そこはRubyのガベージコレクションで適宜開放されるのでしょう。
次のコードをenvironment.rbに追加すると、dependencies.rbでクラスが読み込まれたり、削除されたりするのをコマンドプロンプトで観察できます。
ActiveSupport::Dependencies.logger = Rails.logger ActiveSupport::Dependencies.log_activity = true
Rails以外のプログラムでも、ActiveSupportを使うか、dependencies.rbのような機能を自作すれば、「クラス名に対応するソースを自動的に読み込み」ができるでしょう。
Module#constantsとModule.constants
Moduleのインスタンスメソッドconstantsとクラスメソッドconstantsの区別がややこしい。
Module.constantsはModuleじゃなくてKernelに実装してconst_variablesみたいな名前にしたほうがいいんじゃないだろうか。
Rubyの定数は定数ではない
Rubyの定数(大文字で始まる変数)は再代入が可能です。警告は出ますが変更できます。
FOO = "hello" FOO = "world" p FOO
結果:
temp.rb:2: warning: already initialized constant FOO "world"
RubyにはC/C++のような定数は存在せず、大文字で始まる変数は「クラスに属し、Foo::Barのように外から値を取り出せる変数」であると思われます(トップレベルの定数はObjectに属します)。
「大文字で始まる変数は定数」というより、「大文字で始まる変数はおもに定数として使われている」が正確なところでしょうか。
大文字で始まる変数は「クラス変数」と呼んでもよさそうに思いますが、@@fooのように「クラスとインスタンスの内部で共有される変数」が「クラス変数」と呼ばれています。
Rubyの == と equal? と === と eql? のまとめ
==
数値、文字列、配列などで、「等しいかどうか」「同じ内容かどうか」を調べるのに使います。別のオブジェクト(別のインスタンスへの参照)でも同じ内容ならtrueになります。
Rubyでは数値と文字列の間の自動変換は行われませんので、1 == "1"はfalseです。整数と浮動小数点数の間では自動変換が行われ、1 == 1.0はtrueになります。
自作のクラスのオブジェクトどうしを ==で比較したいときは、==メソッドを定義します。==を定義しないと、equal?と同じく「同じオブジェクトかどうか」になります。
equal?
常に「同じオブジェクトかどうか」を調べるのに使います。自作のクラスでequal?を定義してはいけません(定義できてしまいますが)。
===
case式で値をテストするのに使われます。case a ; when b とするとb === aが評価されます。通常は==の結果と同じですが、Module、Range、Regexpでは別の意味になります。
===はEnumerable#grepでも使われます(ここ忘れないこと>俺)。
===を直接使うことはありませんが、Module、Range、Regexpで使うのはありかな?(is_a?やkind_of?のかわりに String === obj とするとか)。自作のクラスで===を定義することはほとんどないでしょう。
ちなみに、PHPでは 1 == "1"は真、1 === "1"は偽、のように===は「等しく型も同じ」を調べるのに使います。JavaScriptも同様。