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を書くと次のようになります。
(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されるみたいですね。
Validations#validatesの簡易版を実装
ActiveSupport::(Callbacks, Concern)のいい加減なのができたので、validatesのいい加減なのを作ってみました。
複数のモジュールにある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