module Components def self.included(base) #:nodoc: base.class_eval do include InstanceMethods extend ClassMethods helper HelperMethods # If this controller was instantiated to process a component request, # +parent_controller+ points to the instantiator of this controller. attr_accessor :parent_controller alias_method_chain :process_cleanup, :render_component alias_method_chain :session=, :render_component alias_method_chain :flash, :render_component alias_method_chain :assign_shortcuts, :render_component alias_method_chain :send_response, :render_component alias_method :component_request?, :parent_controller end end module ClassMethods # Track parent controller to identify component requests def process_with_components(request, response, parent_controller = nil) #:nodoc: controller = new controller.parent_controller = parent_controller controller.process(request, response) end end module HelperMethods def render_component(options) @controller.__send__(:render_component_as_string, options) 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) #:nodoc: flash.discard if component_request? process_without_components(request, response, method, *arguments) end def send_response_with_render_component response.prepare! unless component_request? response end protected # Renders the component specified as the response for the current method def render_component(options) #:doc: component_logging(options) do render_for_text(component_response(options, true).body, response.headers["Status"]) end end # Returns the component response as a string def render_component_as_string(options) #:doc: 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 def flash_with_render_component(refresh = false) #:nodoc: if !defined?(@_flash) || refresh @_flash = if defined?(@parent_controller) @parent_controller.flash else flash_without_render_component end end @_flash end private def component_response(options, reuse_response) klass = component_class(options) request = request_for_component(klass.controller_path, options) new_response = reuse_response ? response : response.dup klass.process_with_components(request, new_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_path, options) new_request = request.dup new_request.session = request.session new_request.instance_variable_set( :@parameters, (options[:params] || {}).with_indifferent_access.update( "controller" => controller_path, "action" => options[:action], "id" => options[:id] ) ) new_request end def component_logging(options) if logger logger.info "Start rendering component (#{options.inspect}): " result = yield logger.info "\n\nEnd of component rendering" result else yield end end def session_with_render_component=(options = {}) session_without_render_component=(options) unless component_request? end def process_cleanup_with_render_component process_cleanup_without_render_component unless component_request? end def assign_shortcuts_with_render_component(request, response) assign_shortcuts_without_render_component(request, response) flash(:refresh) flash.sweep if @_session && !component_request? end end end