「respond_toを調べてみた」の編集履歴(バックアップ)一覧はこちら
「respond_toを調べてみた」(2012/01/09 (月) 08:11:19) の最新版変更点
追加された行は緑色になります。
削除された行は赤色になります。
どうにも、Railsのrespond_toメソッドがなんでこんな記述で動くのか良く判らなかったので、じっくり調べてみた。
-基本の使い方のおさらい
--まずは、コントローラにこんな感じで書いて、
def index
@books = Book.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @books }
end
end
--で、http://localhost:3000/books にアクセスすればHTMLが返却され、
--http://localhost:3000/books.json にアクセスすればJSONが返却される、と。
-たぶん、yieldを使って書いてありそうなんだけど、ちょっと理解しがたいのでRailsのソースを見てみた。
--まずは、当たりも付かないので、Rubyの下をソースを全grepして、怪しいファイルを発見。うーん、かっこ悪いw
lib\ruby\gems\1.9.1\gems\actionpack-3.1.0\lib\action_controller\metal\mime_responds.rb(42): def respond_to(*mimes)
lib\ruby\gems\1.9.1\gems\actionpack-3.1.0\lib\action_controller\metal\mime_responds.rb(191): def respond_to(*mimes, &block)
--まあ、この「mime_responds.rb」が怪しいっぽいので、ソースを見ると…
include ActionController::ImplicitRender
included do
class_attribute :responder, :mimes_for_respond_to
self.responder = ActionController::Responder
clear_respond_to
end
module ClassMethods
def respond_to(*mimes)
options = mimes.extract_options!
only_actions = Array(options.delete(:only))
except_actions = Array(options.delete(:except))
new = mimes_for_respond_to.dup
mimes.each do |mime|
mime = mime.to_sym
new[mime] = {}
new[mime][:only] = only_actions unless only_actions.empty?
new[mime][:except] = except_actions unless except_actions.empty?
end
self.mimes_for_respond_to = new.freeze
end
def clear_respond_to
self.mimes_for_respond_to = ActiveSupport::OrderedHash.new.freeze
end
end
def respond_to(*mimes, &block)
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
if response = retrieve_response_from_mimes(mimes, &block)
response.call(nil)
end
end
def retrieve_response_from_mimes(mimes=nil, &block) #:nodoc:
mimes ||= collect_mimes_from_class_level
collector = Collector.new(mimes) { |options| default_render(options || {}) }
block.call(collector) if block_given?
if format = request.negotiate_mime(collector.order)
self.content_type ||= format.to_s
lookup_context.freeze_formats([format.to_sym])
collector.response_for(format)
else
head :not_acceptable
nil
end
end
def collect_mimes_from_class_level #:nodoc:
action = action_name.to_sym
self.class.mimes_for_respond_to.keys.select do |mime|
config = self.class.mimes_for_respond_to[mime]
if config[:except]
!action.in?(config[:except])
elsif config[:only]
action.in?(config[:only])
else
true
end
end
end
---む、これっぽいか?
---おお?これって、ClassMethods::respond_to(*mimes)って、使ってる?うーん、良く判らん…。
-ソース解析すると、こんな感じか?
--まず、respond_to(*mimes, &block)が呼び出され、その中でmimesがあってかつblockが渡されてれば、retrieve_response_from_mimesを実行
--retrieve_response_from_mimesだと、渡されたmimesが正しいかをcollect_mimes_from_class_levelでチェックして、collector を生成
---collector は、mimesの分だけdefault_renderを使ってoptionsを作る
--その後、block_given?でブロックがあれば、block.call(collector)で「ブロックで指定されたメソッドを実行」
---なんだけど、どうやって「ブロックで指定されたメソッドを実行」してるんだ?
-あ、判った。Rubyだとメソッド呼び出しでのdo ~ endが結構特殊なんだ!
--例えば、こんなメソッドを定義して、
def foo(p1,p2,&block)
block.call(p1,p2)
end
--こんなコードで呼び出すと、
foo("p1","p2") do |p1,p2|
p p1
p p2
end
--こんな感じで表示される
"p1"
"p2"
--これは、block.call(p1,p2)実行時に、fooのパラメータのp1,p2で、do~end内が繰り返されずに実行されるんだねー。
---だから、fooをこんな感じで書くと
foo("p1","p2") do |p1,p2|
p p2
p p1
p "dummy"
end
---こんな感じで表示されるんだ!
"p2"
"p1"
"dummy"
-しかし、結局mimesとかは、何が入ってるんだか、良く判らんからちゃんと見てみると?
--まずは、respond_to(*mimes, &block)で呼び出される時は、*mimesとなっている
---*配列は、「配列でもらって、使う時は展開する」書式なんだー
---なので、ここでは、retrieve_response_from_mimes(mimes=nil, &block)の時に、mimesの配列が列挙されるんだねー。
--で、retrieve_response_from_mimes(mimes=nil, &block)では、列挙されたmimesが渡されてるはず、と。
---あー、だから、retrieve_response_from_mimes内では、 以下のコードで「mime」のひとつずつに対して、true/false判定するんだねー
mimes ||= collect_mimes_from_class_level
どうにも、Railsのrespond_toメソッドがなんでこんな記述で動くのか良く判らなかったので、じっくり調べてみた。
-基本の使い方のおさらい
--まずは、コントローラにこんな感じで書いて、
def index
@books = Book.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: @books }
end
end
--で、http://localhost:3000/books にアクセスすればHTMLが返却され、
--http://localhost:3000/books.json にアクセスすればJSONが返却される、と。
-たぶん、yieldを使って書いてありそうなんだけど、ちょっと理解しがたいのでRailsのソースを見てみた。
--まずは、当たりも付かないので、Rubyの下をソースを全grepして、怪しいファイルを発見。うーん、かっこ悪いw
lib\ruby\gems\1.9.1\gems\actionpack-3.1.0\lib\action_controller\metal\mime_responds.rb(42): def respond_to(*mimes)
lib\ruby\gems\1.9.1\gems\actionpack-3.1.0\lib\action_controller\metal\mime_responds.rb(191): def respond_to(*mimes, &block)
--まあ、この「mime_responds.rb」が怪しいっぽいので、ソースを見ると…
include ActionController::ImplicitRender
included do
class_attribute :responder, :mimes_for_respond_to
self.responder = ActionController::Responder
clear_respond_to
end
module ClassMethods
def respond_to(*mimes)
options = mimes.extract_options!
only_actions = Array(options.delete(:only))
except_actions = Array(options.delete(:except))
new = mimes_for_respond_to.dup
mimes.each do |mime|
mime = mime.to_sym
new[mime] = {}
new[mime][:only] = only_actions unless only_actions.empty?
new[mime][:except] = except_actions unless except_actions.empty?
end
self.mimes_for_respond_to = new.freeze
end
def clear_respond_to
self.mimes_for_respond_to = ActiveSupport::OrderedHash.new.freeze
end
end
def respond_to(*mimes, &block)
raise ArgumentError, "respond_to takes either types or a block, never both" if mimes.any? && block_given?
if response = retrieve_response_from_mimes(mimes, &block)
response.call(nil)
end
end
def retrieve_response_from_mimes(mimes=nil, &block) #:nodoc:
mimes ||= collect_mimes_from_class_level
collector = Collector.new(mimes) { |options| default_render(options || {}) }
block.call(collector) if block_given?
if format = request.negotiate_mime(collector.order)
self.content_type ||= format.to_s
lookup_context.freeze_formats([format.to_sym])
collector.response_for(format)
else
head :not_acceptable
nil
end
end
def collect_mimes_from_class_level #:nodoc:
action = action_name.to_sym
self.class.mimes_for_respond_to.keys.select do |mime|
config = self.class.mimes_for_respond_to[mime]
if config[:except]
!action.in?(config[:except])
elsif config[:only]
action.in?(config[:only])
else
true
end
end
end
---む、これっぽいか?
---おお?これって、ClassMethods::respond_to(*mimes)って、使ってる?うーん、良く判らん…。
-ソース解析すると、こんな感じか?
--まず、respond_to(*mimes, &block)が呼び出され、その中でmimesがあってかつblockが渡されてれば、retrieve_response_from_mimesを実行
--retrieve_response_from_mimesだと、渡されたmimesが正しいかをcollect_mimes_from_class_levelでチェックして、collector を生成
---collector は、mimesの分だけdefault_renderを使ってoptionsを作る
--その後、block_given?でブロックがあれば、block.call(collector)で「ブロックで指定されたメソッドを実行」
---なんだけど、どうやって「ブロックで指定されたメソッドを実行」してるんだ?
-あ、判った。Rubyだとメソッド呼び出しでのdo ~ endが結構特殊なんだ!
--例えば、こんなメソッドを定義して、
def foo(p1,p2,&block)
block.call(p1,p2)
end
--こんなコードで呼び出すと、
foo("p1","p2") do |p1,p2|
p p1
p p2
end
--こんな感じで表示される
"p1"
"p2"
--これは、block.call(p1,p2)実行時に、fooのパラメータのp1,p2で、do~end内が繰り返されずに実行されるんだねー。
---だから、fooをこんな感じで書くと
foo("p1","p2") do |p1,p2|
p p2
p p1
p "dummy"
end
---こんな感じで表示されるんだ!
"p2"
"p1"
"dummy"
-しかし、結局mimesとかは、何が入ってるんだか、良く判らんからちゃんと見てみると?
--まずは、respond_to(*mimes, &block)で呼び出される時は、*mimesとなっている
---*配列は、「配列でもらって、使う時は展開する」書式なんだー
---なので、ここでは、retrieve_response_from_mimes(mimes=nil, &block)の時に、mimesの配列が列挙されるんだねー。
--で、retrieve_response_from_mimes(mimes=nil, &block)では、列挙されたmimesが渡されてるはず、と。
---あー、だから、retrieve_response_from_mimes内では、 以下のコードで「mime」のひとつずつに対して、true/false判定するんだねー
mimes ||= collect_mimes_from_class_level
----
#counter