Dynamic Finder

Ruby On Rails の Dynamic Finderの仕組みが不思議です。

BOOKSテーブルに対応するモデルBookクラスに対して

Book.find_by_isbn('1234')

とかやると、BOOKSテーブルから値が返されます。

select * from BOOKS where isbn = '1234'

と等価です。

一方、Bookクラスの実装はというと

class Book < ActiveRecord::Base
end

なわけです。find_by_isbn なんてメソッド定義がありません。

Javaの場合、少なくともインタフェース定義しないとメソッドは呼べません。

どうやってるんでしょうか。

Rubyの場合、呼び出したメソッドが存在しないと、そのクラスの method_missing というメソッドに処理が委譲されるんですね。

その仕組を利用して、method_missing の中で、find_by_isbn の字句を解析して、SQLを組み立てているようです。
method_missing なんて、動的型付け言語らしい機能です。
こういうのがRubyの面白いところなのかな。

ActiveRecord3.0.9のbase.rbの中を見て見ました。

def method_missing(method_id, *arguments, &block)
  if match = DynamicFinderMatch.match(method_id)
    attribute_names = match.attribute_names
    super unless all_attributes_exists?(attribute_names)
    if match.finder?
      options = arguments.extract_options!
      relation = options.any? ? construct_finder_arel(options, current_scoped_methods) : scoped
      relation.send :find_by_attributes, match, attribute_names, *arguments

このコードを読み解く力はまだ私にはありません。
finderの命名規約にマッチした場合に、finder処理を組み立てているんだ、というのはなんとなく読み取れます。

ちなみに、Book.find_by_isbnのBOOKSテーブルとisbnカラムがDBに存在することはいつチェックするんですかね?Railsの中でDB定義のコンフィグとかは持っていないように見えるから、とりあえずSQLを投げてしまうのだろうか?あるいは、DBのメタデータにアクセスする?
上のコードを読み進めていけばそのうちわかるのでしょう。がんばります。

それにしても、変数の型がすぐにわからないから、どうやってコードを追っていけばいいのか、まだよくわかってない。一つ一つクラス定義を探しながらやっていくしかないんだよね。こういうところはJavaより少し面倒だ。