Railsの開発環境の自動再読み込みはどうやってるのか

以前から気になっていたけど調べたことのなかったRailsのアノ機能を調べてみました。開発環境でソースを修正→ブラウザーをリロード→変更が反映、の機能です。

どうやっているかというと、RubyのModule#const_missingとModule#remove_constを使っています。ソースコードactivesupport-x.x.x/lib/active_support/dependencies.rbにあります。

  1. Module#const_missingを定義する。
    1. 存在しないクラス名(定数名)にぶつかると、const_missingが呼ばれる。
    2. const_missingでは、クラス名をファイル名に変換し、ディレクトリ(app/controllers、app/models、libなど)を調べて、ファイルがあればrequireまたはloadする。
    3. 新しいクラス名が定義されれば、名前を保存しておく。
  2. リロードされたら(HTTPリクエストが来たら)、保存したクラス名(定数名)をすべてModule#remove_constで削除する。
    1. 1-1.に戻る。

ソースファイルのタイムスタンプを見ているかと思ってましたが、そこまではしていないようです。

リロードするたびにクラスオブジェクトがメモリに溜まっていきそうな感じですが、そこはRubyガベージコレクションで適宜開放されるのでしょう。

次のコードをenvironment.rbに追加すると、dependencies.rbでクラスが読み込まれたり、削除されたりするのをコマンドプロンプトで観察できます。

ActiveSupport::Dependencies.logger = Rails.logger
ActiveSupport::Dependencies.log_activity = true

Rails以外のプログラムでも、ActiveSupportを使うか、dependencies.rbのような機能を自作すれば、「クラス名に対応するソースを自動的に読み込み」ができるでしょう。

Rubyの定数は定数ではない

Rubyの定数(大文字で始まる変数)は再代入が可能です。警告は出ますが変更できます。

FOO = "hello"
FOO = "world"
p FOO

結果:

temp.rb:2: warning: already initialized constant FOO
"world"

RubyにはC/C++のような定数は存在せず、大文字で始まる変数は「クラスに属し、Foo::Barのように外から値を取り出せる変数」であると思われます(トップレベルの定数はObjectに属します)。

「大文字で始まる変数は定数」というより、「大文字で始まる変数はおもに定数として使われている」が正確なところでしょうか。

大文字で始まる変数は「クラス変数」と呼んでもよさそうに思いますが、@@fooのように「クラスとインスタンスの内部で共有される変数」が「クラス変数」と呼ばれています。

1999年のメーリングリストアーカイブで定数についてのまつもと氏の考えが読めます。

Rubyの == と equal? と === と eql? のまとめ

==

数値、文字列、配列などで、「等しいかどうか」「同じ内容かどうか」を調べるのに使います。別のオブジェクト(別のインスタンスへの参照)でも同じ内容ならtrueになります。

Rubyでは数値と文字列の間の自動変換は行われませんので、1 == "1"はfalseです。整数と浮動小数点数の間では自動変換が行われ、1 == 1.0はtrueになります。

自作のクラスのオブジェクトどうしを ==で比較したいときは、==メソッドを定義します。==を定義しないと、equal?と同じく「同じオブジェクトかどうか」になります。

equal?

常に「同じオブジェクトかどうか」を調べるのに使います。自作のクラスでequal?を定義してはいけません(定義できてしまいますが)。

Javaでは、Stringなどのequalsメソッドは「同じ内容かどうか」を調べるもので、Rubyと逆です。

===

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も同様。

eql?

ハッシュの中で「ハッシュのキーとして同じかどうか」を調べるのに使われます。eql?を直接使うことはありません。

自作のクラスでは、「内容が同じならハッシュでは同じキーとして扱ってほしい」ときはeql?を定義します。eql?の定義のしかたは、オライリーの『プログラミング言語Ruby』p232に詳しい解説があります。