ctrl+cでrackサーバーをシャットダウンする

Rack.version == 1.1で、

require 'rack'
Rack::Handler::WEBrick.run HelloRack.new, :Port => 9292

とするとctrl+cでサーバーがシャットダウンできないので、次のように書き換えてシャットダウンできるようにしています。

rackアプリは設定ファイル(hello.ru)のなかで指定しています。設定ファイルには"run HelloRack.new"が書かれていますが、このrunはRack::Builderのメソッドです。
Rack::Server#startを見るとServer#appを呼ばれていて、その中でRack::Builder::parse_fileが呼ばれています。ここで、指定した設定ファイルをinstance_evalしているようです。結局rackアプリがServer#appから得られます。appはRack::Handler(webrick的にはServlet)に渡されて...あとの処理はwebrick(標準ライブラリ)を見ると分かります。


ところでRails::Server(書いたときの最新のコード)のstartメソッドはrackが自力でshutdownできるのにtrap {exit}を書いていて無駄かなと思います。

Rails::Initializable::Collectionのtsort_each

tsort(トポロジカルソート)は主に依存関係の解決に利用されるようです。この処理の前には、あの処理をやっておくというように指示すると、tsortはその条件に合うようにソートします。次のコードはRailsの初期化処理とほぼ同じ実装で、:afterまたは:beforeで依存関係を指示しています。コード内の例を:beforeから:afterに線を引いてDAGを書くと次のようになります。
http://upload.wikimedia.org/wikipedia/commons/thumb/0/08/Directed_acyclic_graph.png/180px-Directed_acyclic_graph.png(wikipediaより)

tsort_each_node(&block)はnodeを返し、tsort_each_child(node,&block)はnodeのchild、すなわち矢印の元になっているnodeの配列childrenを作ってchildren.each(&block)を処理させます。そうすると、tsort_each {|i| なにか処理 }をするとiには依存関係を守った順序で結果を返します。

コントローラーとビューの連携

本物は色々なモジュールに分かれてたり、継承してたりするわけですが、究極に単純化するとコントローラーがview_contextというオブジェクトを持っていて、renderを呼ぶタイミングでTemplateクラスを通してerbファイルがコンパイルされて、ソースはview_contextオブジェクトのメソッドに追加されます。最後にコントローラーオブジェクトのインスタンス変数のようなもの(@response_body)に記録されます。

あぁ、あとはview_context_classが作られるときに、ヘルパー関数もview_contextのメソッドとして使えるように動的にincludeされるみたいですね。

複数のモジュールにあるStaticeMethodsを1つのクラスのクラスメソッドにまとめる

1つ前までのConcernではうまくいかないので修正。これでほぼrailsのConcernと同じです。

解説

まずMとM2はConcernをextendしたときに@dependenciesが定義されます。"include M"で、M.append_feachers(M2)が呼ばれます。M2は@dependenciesをinstance variableとして持つので@dependenciesにMが追加されます。

class CがM2をincludeすると、M2.append_featues(C)が呼ばれます。Cは@dependenciesを持たないので、M2をincludeします(super)。それから@depandenciesに入っているモジュールをCは全てincludeします(今回はMだけ)。最後にC.extend M2::StaticMethodsが実行されるので、M2::StaticMethodsに定義されているメソッドがCのクラスメソッドになります。

CがMをincludeしたタイミングでM.append_features(C)が呼ばれます。M2と同様にM::StaticMethodsがextendされて、M::StaticMethodsはCのクラスメソッドになります。

callbacksの簡易版を実装

いやぁ、Ruby on Rails Guilds:Get Startedをやってみて、validationまわりがどうなっているのか気になったので読んでみました。validatesはActive SupportのCallbacksモジュールのset_callbackでValidatorオブジェクトがvalidateをコールするのをvalid?を実行するまで遅延させていたので、まずはそっちの方を読みながら簡易版を作りました。

#callbacks.rb
class Class
  def class_attribute name
    class_eval <<-RUBY, __FILE__, __LINE__ + 1
      def self.#{name}() nil end
      def self.#{name}=(val)
        singleton_class.class_eval {
          undef_method :#{name}
          define_method(:#{name}) { val }
        }
        val
      end 
    RUBY
  end
end
module MyRails
  module Concern
    def append_features(base)
      base.extend const_get("ClassMethods") 
      super
    end
  end
  module Callbacks
    extend Concern
    class Callback
      @@_callback_sequence = 0
      attr_reader :filter
      def initialize(callback_name, callback_obj, callbacks_class)
        @filter = "_callback_#{callback_name}_#{next_id}"
        callbacks_class.send(:define_method, "#{@filter}_object") { callback_obj }
        callbacks_class.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1
          def #{@filter}
            #{@filter}_object.#{callback_name}
          end
        RUBY_EVAL
      end
      def next_id
        @@_callback_sequence += 1
      end
    end
    def run_callbacks(callback_name)
      self.class.send("_#{callback_name}_callbacks").each do|callback|
        send(callback.filter)
      end
    end
    module ClassMethods
      def define_callback(callback_name)
        attr_name = "_#{callback_name}_callbacks"
        class_attribute attr_name
        send("#{attr_name}=", Array.new)
      end
      def set_callback(callback_name, callback_obj)
        send("_#{callback_name}_callbacks").push(Callback.new(callback_name, callback_obj, self))
      end
    end
  end
end

class_evalは文字列も受け付けるんですね。テストはこんな感じ。

require './callbacks'
require 'test/unit'
require 'stringio'
class C 
  class_attribute :name 
end
class CallbackObj
  def callback_method
    puts "1"
  end
end
class CallbackObj2
  def callback_method
    puts "2"
  end
end
class Callbacks
  include MyRails::Callbacks
  define_callback :callback_method
  set_callback(:callback_method, CallbackObj.new)
  set_callback(:callback_method, CallbackObj2.new)
end
class MyRailsTests < Test::Unit::TestCase
  def test_class_attribute
     name = "nabeyang"
     assert_equal name, (C.name = name)
     assert_equal name, C.name
  end
  def test_callbacks_set_callback
    assert_equal 2, Callbacks._callback_method_callbacks.size
  end
  def test_callbacks_run_callback
    obj = Callbacks.new
    s = StringIO.new
    $stdout = s 
    obj.run_callbacks(:callback_method)
    $stdout = STDOUT
    assert_equal "1\n2\n", s.string
  end
end

1つのモジュールの中で特異メソッドとインスタンスメソッドを定義して、クラスに引き渡す方法

active supportを使うと、次のようになります。

require 'active_support/concern'
module M
  extend ActiveSupport::Concern
  def method2
    puts "method2"
  end
  module ClassMethods
    def method1
      puts "method1"
    end
  end
end
 
class C 
  include M 
end
C.method1 #=> method1
C.new.method2 #=> method2

これはextendとincludeを組み合わせてやっています。

METHOD_LIST = []
module XXXMethod
  (1..3).each do|i| 
    fn = "method#{i}"
    METHOD_LIST << fn.to_sym
    define_method(fn) do
      if self.class.to_s == "Class"
        puts "static method:#{fn}"
      else
        puts "instance method:#{fn}"
      end
    end
  end
end

class UseXXXAsStaticMethod
  extend XXXMethod
end
class UseXXXAsInstanceMethod
  include XXXMethod
end

obj =  UseXXXAsInstanceMethod.new
METHOD_LIST.each do|fn|
  UseXXXAsStaticMethod.__send__ fn 
  obj.__send__ fn
end

を実行してみると分かるのですが、extendはモジュールのメソッドをクラスの特異メソッドとして引き渡し、includeはインスタンスメソッドとして引き渡します。
Moduleの特異メソッドのappend_featuresはincludeされたときに呼ばれるメソッドで、ActiveSupport::Concernはこれを利用しています。まずappend_featuresの動作を次のコードで確認します。

module M 
  def self.append_features(base)
    puts "self:#{self}, base:#{base}"
    super
  end
end
class C
  include M
end
#=> self:M, base: C

それでは、Concernの簡易版を作ります。ConcernをextendしたモジュールMは、クラスCにincludeされると、Concernで定義したappend_features(base)が呼ばれます。ここで、baseはCになっているので、base.extend StaticMethodsすることで、特異メソッドを定義し、superでインスタンスメソッドを引き渡せます(superはincludeのデフォルトの動作)。

module Concern 
  def append_features(base)
    base.extend const_get("StaticMethods") if const_defined?("StaticMethods")
    super
  end
end
module M
  extend Concern 
  def say_hello 
    puts "hello"
  end
  module StaticMethods
    def method1 
      puts "1" 
    end
  end
end
module StaticMethods
  def method1
    puts "2"
  end
end
class C
  include M
end
C.method1 #=>1
C.new.say_hello #=> hello