人気ブログランキング | 話題のタグを見る

RoR config/boot.rb class << self

いろいろと道草を食ったがいよいよ、config/boot.rb に挑戦することにした。何度も書いているが、boot.rb の構成は次のようになっている。

RAILS_ROOT の設定
Rails モジュールの定義
Rails.boot! の実行

今回はその Rails モジュールの中身を見てみよう。Rails モジュールの中身は全てクラスの定義だ。どういうクラスかというと、次のようになる。

module Rails
  class << self
  class Boot
  class VendorBoot < Boot
  class GemBoot < Boot
end

module Rails の中身は全てクラスやメソッドの定義だけだ。実行は、Rails.boot! をきっかけに次々にオブジェクトやメソッドを呼び出していくことになる。また、実装は Rails モジュールの中にカプセル化され、外部からはそれを気にする必要がない。

こうやって外側から眺めてあれこれ言っていても話は進まないので、面倒くさいなという思いに逆らって中を覗いてみることにしよう。最初は class << self で定義される一連のクラスメソッドを見てみる。boot.rb は 最後に、Rails.boot! が実行されることで一連の処理が始まるが、その Rails.boot! クラスメソッドの定義がここにあるからだ。

次が、boot.rb から class << self の部分を抜き出したものだ。

module Rails
  class << self
    def boot!
      unless booted?
        preinitialize
        pick_boot.run
      end
    end

    def booted?
      defined? Rails::Initializer
    end

    def pick_boot
      (vendor_rails? ? VendorBoot : GemBoot).new
    end

    def vendor_rails?
      File.exist?("#{RAILS_ROOT}/vendor/rails")
    end

    def preinitialize
      load(preinitializer_path) if File.exist?(preinitializer_path)
    end

    def preinitializer_path
      "#{RAILS_ROOT}/config/preinitializer.rb"
    end
  end

これを見ると、class << self の構成は次のようになっている。

class << self
  def boot!
    unless booted?
      preinitialize
      pick_boot.run
    end
  def booted?
  def pick_boot
  def vendor_rails?
  def preinitialize
  def preinitializer_path
end

boot! メソッドだけは中身を省略しなかった。boot.rb の動作は Rails.boot! から始まっているからだ。上の構成図を見ると boot.rb はそう難しいことをしているわけではないのが分かる。

boot! メソッドで実行しているのは、既にブートされているかどうかをチェックして(booted?)、ブートがまだのようだったら、本初期化の前の前段階の初期化をして(preinitialize)、vendor boot、か gem boot かを選んで、Rails本体の初期化を呼び出す(pick_boot)だけだ。

ただ、奇妙なのは普通の手続き型のプログラムと違って、やたらに関数化されているということだ。事実、boot! メソッド以外のメソッドの中身は皆、たった一行だ。これは著者がやたらと関数を作りたがる関数プログラミングの信奉者か、それともこの奇妙なスタイルに深い意味があるのかのどちらかだ。

ところで、プログラムを関数で定義することの利点の一つは、関数の記述は前後関係を問わないということだ。前回のエントリーで述べたように、代入の代わりに関数を用いると、これはコードの置き場所をどこに置いてもよくなるため、アルゴリズムの本質に関係ないパーサの側の事情による前後関係を考えることから開放される。次の例のような記述は、class << self のどこに置いても構わないのだ。

    def preinitializer_path
      "#{RAILS_ROOT}/config/preinitializer.rb"
    end

したがって、boot! のような抽象的なメソッドの定義を先頭に持ってくることができる。はじめに、こういうことをやるよと記述して、段々にそれに必要な部品を作っていくというような、トップダウンの記述ができる。また、読み返すときもそのモジュールが何をやっているのかは先頭の関数を見れば大体分かるので読解が楽になる。

また、プログラムの変更があっても部品の関数の変更だけで、メインの boot! 関数にまったく手を入れる必要がない。関数としてカプセル化されることによってコードの部品化が容易になるからだ。

class << self に限らず、boot.rb の記述は全体としてこのようなトップダウンの記述がなされている。

class << self の個々のメソッドの内容については、ソースを読めば分かるように書いてあるので、これ以上立ち入らない。しかし、class << self の次のメソッドについては、Ruby の使い方がおもしろかったので付け加える。

    def pick_boot
      (vendor_rails? ? VendorBoot : GemBoot).new
    end

pick_boot メソッドは、vendor の Rails があれば(vendor_rails?) それを実行し、なければ、RubyGems の Rails を実行するという動作をするが、おもしろいのは、

      (vendor_rails? ? VendorBoot : GemBoot).new

の部分だ。三項演算子を使って、vender_rails? メソッドが真を返せば VendorBoot クラスを、そうでなければ、GemBoot クラスを返させている。値として帰ったクラスには new メソッドが適用され、結果的には pick_boot 関数は、VendorBoot クラスのインスタンスか、GemBoot クラスのインスタンスが返ることになる。おもしろいのは、クラスをオブジェクトのように扱っているところだ。

一体こんなことが可能なのだろうかと思って、irb で実験してみた。

irb(main):001:0> class A
irb(main):002:1> def say; 'hello'; end
irb(main):003:1> end
=> nil
irb(main):004:0> a = A.new; a.say
=> "hello"
irb(main):005:0> b = A; b.new.say
=> "hello"
irb(main):006:0>

ほんとうに、変数 b に、クラスAが代入できて、b からインスタンスを作ることができた。Rubyは、クラス までがオブジェクトなのだ。

ここまで調べたことをまとめると、Rails.boot! メソッドは Rails メソッドが既にブートされているかどうかをチェックし、ブートされていなければ vendor の Rails があるかどうかを調べ、あれば VendorBoot クラスのオブジェクトに run のメッセージを送り、なければ、GemBoot クラスのオブジェクトに run メッセージを送るという動作をしていることになる。

したがって、次に考えるのは VendorBoot オブジェクトや、GemBoot オブジェクトとはなにかということになるが、それは次の記事で調べてみる。
by tnomura9 | 2008-10-18 18:15 | Ruby | Comments(0)
<< RoR config/boot... RoR script/server >>