This article was migrated from http://rails.office.drecom.jp/takiuchi/archive/39
render_componentの挙動を調査する必要があったので、メモを残しておきます。
render_componentは、いわゆるWidgetのような、再利用可能なGUI部品をレンダリングするメソッドです。以下に、要点だけ抜き出したソースを示します。
長いので先に結論だけ書いておくと、render_componentでレンダリングされるコンポーネントのコードから、呼び出し元の外部コントローラにアクセスするには、ActionController#parent_controllerを介せばOKです。
$ vim vendor/rails/actionpack/lib/action_controller/components.rb
module ActionController
module Components
base.helper do
def render_component(options)
@controller.send(:render_component_as_string, options)
end
end
module ClassMethods
# Track parent controller to identify component requests
def process_with_components(request, response, parent_controller = nil)
controller = new
controller.parent_controller = parent_controller
controller.process(request, response)
end
end
module InstanceMethods
# Extracts the action_name from the request parameters and performs that action.
def process_with_components(request, response, method = :perform_action, *arguments)
flash.discard if component_request?
process_without_components(request, response, method, *arguments)
end
protected
# Returns the component response as a string
def render_component_as_string(options)
component_logging(options) do
response = component_response(options, false)
if redirected = response.redirected_to
render_component_as_string(redirected)
else
response.body
end
end
end
private
def component_response(options, reuse_response)
klass = component_class(options)
request = request_for_component(klass.controller_name, options)
response = reuse_response ? @response : @response.dup
klass.process_with_components(request, response, self)
end
# determine the controller class for the component request
def component_class(options)
if controller = options[:controller]
controller.is_a?(Class) ?
controller :
"#{ controller.camelize }Controller".constantize
else
self.class
end
end
# Create a new request object based on the current request.
# The new request inherits the session from the current request,
# bypassing any session options set for the component controller's class
def request_for_component(controller_name, options)
request = @request.dup
request.session = @request.session
request.instance_variable_set(
:@parameters,
(options[:params] || {}).with_indifferent_access.update(
"controller" => controller_name, "action" => options[:action],
"id" => options[:id]))
request
end
end
end
request_for_componentの中身:
with_indifferent_accessは、ActiveSupportによるHashの拡張機能で、Symbolと文字列のkeyを同一視できるようにするものです。instance_variable_setというのは、RubyのObjectクラスのメソッドで、こんな感じにインスタンス変数を外部から設定できてしまいます:
obj = Object.new
p obj.instance_variable_set("@foo", 1) # => 1
p obj.instance_variable_set(:@foo, 2) # => 2
p obj.instance_variable_get(:@foo) # => 2
というわけで、上記のようにcontroller、action、idの指定を挿げ替えてrequestオブジェクトを偽装しているわけです。
そして、最終的にklass.process_with_components(request, response, self)が呼び出され、render_componentの呼び出し元のcontrollerをparent_controllerに設定し、制御を実行しています。
したがって、レンダリングされるコンポーネントの内部からは、parent_controllerを介して呼び出し元のcontrollerとやり取りを行う事ができます。
This article was migrated from http://rails.office.drecom.jp/takiuchi/archive/39