DataMapper 0.10.2 Reading (その 2: dm-core.rb)

やはり,最初はここから.
0.9.x に比べて,格段にモジュール化が進み,非常にシンプルになっている.

最初の方

ひたすら require して,必要なコードを読み込んでいる.所々 TODO があるので,また変わるんだろうなぁ...

DataMapper.root

module DataMapper
  def self.root
    @root ||= Pathname(__FILE__).dirname.parent.expand_path.freeze
  end

dm-core パッケージのディレクトリパスを設定する.確かに freeze しておくのは手だなぁ.

DataMapper.setup

なんかシンプルになってるし...

module DataMapper
  def self.setup(*args)
    adapter = args.first

    unless adapter.kind_of?(Adapters::AbstractAdapter)
      adapter = Adapters.new(*args)
    end

    Repository.adapters[adapter.name] = adapter
  end

args は可変長引数なので Array インスタンスである.setup メソッドの呼び出し方が

DataMapper.setup(:default, 'mysql://localhost/dm_test')
DataMapper.setup(:default, {
  :adapter  => 'postgres',
  :database => 'localhost/dm_test',
  ...
})

であることから,ローカル変数 adapter はコンテキスト (Repository) を識別するための Symbol が入る.adapter が AbstractAdapter 派生のインスタンスでなければ (←通常は Symbol なのでこのパターン),引数 args から新しい Adapter を作成する (MysqlAdapter 等).Adapters.new メソッド内部では,以前紹介した DataMapper.setup メソッドのように uri/Hash から Adapter インスタンスを作成しているが,このコードがまたきれいにまとめられていて,とても読みやすい (Adapters についてはまた今度).

最後は name を key として Adapter インスタンスが Repository.adapters に蓄えられる.adapter.name は,setup メソッドの第 1 引数に渡した Symbol である.

ところで,adapter.kind_of? で AbstractAdapter かどうかを見ているということは,setup メソッドの引数に Adapter インスタンスが渡せるということか?

DataMapper.repository

このメソッドはあまり変わっていない.ただ,コードが整理され,すっきりしてる.

module DataMapper
  def self.repository(name = nil)
    context = Repository.context

    current_repository = if name
      assert_kind_of 'name', name, Symbol
      context.detect { |repository| repository.name == name }
    else
      name = Repository.default_name
      context.last
    end

    current_repository ||= Repository.new(name)

    if block_given?
      current_repository.scope { |*block_args| yield(*block_args) }
    else
      current_repository
    end
  end

基本的な流れは以下の通り.

  1. context (コンテキストスタック) を取得
  2. 引数に name が指定されていれば,context から名前の一致する Repository インスタンスを取得
  3. 引数に name が指定されていなければ,:default を name とし,context から末尾の Repository インスタンスを取得
  4. context から Repository インスタンスがとれなかった (nil だった) 場合は,新しい Repository インスタンスを生成
  5. block が渡されていれば,block 渡して Repository#scope を呼び出す
  6. block が渡されていなければ,そのまま Repository インスタンスを返す

block 有りの呼び出しは,以下のようにコンテキストを明示して呼び出す際に用いる.

p = DataMapper.repository(:hoge) { Person.first }

block 無しの呼び出しは,主にモデルメソッドの内部におけるコンテキスト取得に用いられる.


Repository.context メソッドは,スレッドローカルから Repository インスタンスの配列 (コンテキストスタック) を取り出す.

module DataMapper
  class Repository
    def self.context
      Thread.current[:dm_repository_contexts] ||= []
    end

Repository#scope メソッドは,Repository.context の返すコンテキストスタックに self を積み,渡された block を実行する.

module DataMapper
  class Repository
    def scope
      context = Repository.context

      context << self

      begin
        yield self
      ensure
        context.pop
      end
    end

これは,yield で実行されるブロックの内部において,モデルメソッドの呼び出しで context 取得が行われた際に,正しい context を返すための処置である.

モジュール/クラス間の関係

無理矢理書いたのでおかしいかも.