| 1 | | module ActionController #:nodoc: |
|---|
| 2 | | # Components allow you to call other actions for their rendered response while executing another action. You can either delegate |
|---|
| 3 | | # the entire response rendering or you can mix a partial response in with your other content. |
|---|
| 4 | | # |
|---|
| 5 | | # class WeblogController < ActionController::Base |
|---|
| 6 | | # # Performs a method and then lets hello_world output its render |
|---|
| 7 | | # def delegate_action |
|---|
| 8 | | # do_other_stuff_before_hello_world |
|---|
| 9 | | # render_component :controller => "greeter", :action => "hello_world", :params => { :person => "david" } |
|---|
| 10 | | # end |
|---|
| 11 | | # end |
|---|
| 12 | | # |
|---|
| 13 | | # class GreeterController < ActionController::Base |
|---|
| 14 | | # def hello_world |
|---|
| 15 | | # render :text => "#{params[:person]} says, Hello World!" |
|---|
| 16 | | # end |
|---|
| 17 | | # end |
|---|
| 18 | | # |
|---|
| 19 | | # The same can be done in a view to do a partial rendering: |
|---|
| 20 | | # |
|---|
| 21 | | # Let's see a greeting: |
|---|
| 22 | | # <%= render_component :controller => "greeter", :action => "hello_world" %> |
|---|
| 23 | | # |
|---|
| 24 | | # It is also possible to specify the controller as a class constant, bypassing the inflector |
|---|
| 25 | | # code to compute the controller class at runtime: |
|---|
| 26 | | # |
|---|
| 27 | | # <%= render_component :controller => GreeterController, :action => "hello_world" %> |
|---|
| 28 | | # |
|---|
| 29 | | # == When to use components |
|---|
| 30 | | # |
|---|
| 31 | | # Components should be used with care. They're significantly slower than simply splitting reusable parts into partials and |
|---|
| 32 | | # conceptually more complicated. Don't use components as a way of separating concerns inside a single application. Instead, |
|---|
| 33 | | # reserve components to those rare cases where you truly have reusable view and controller elements that can be employed |
|---|
| 34 | | # across many applications at once. |
|---|
| 35 | | # |
|---|
| 36 | | # So to repeat: Components are a special-purpose approach that can often be replaced with better use of partials and filters. |
|---|
| 37 | | module Components |
|---|
| 38 | | def self.included(base) #:nodoc: |
|---|
| 39 | | base.class_eval do |
|---|
| 40 | | include InstanceMethods |
|---|
| 41 | | extend ClassMethods |
|---|
| 42 | | |
|---|
| 43 | | helper do |
|---|
| 44 | | def render_component(options) |
|---|
| 45 | | @controller.send!(:render_component_as_string, options) |
|---|
| 46 | | end |
|---|
| 47 | | end |
|---|
| 48 | | |
|---|
| 49 | | # If this controller was instantiated to process a component request, |
|---|
| 50 | | # +parent_controller+ points to the instantiator of this controller. |
|---|
| 51 | | attr_accessor :parent_controller |
|---|
| 52 | | |
|---|
| 53 | | alias_method_chain :process_cleanup, :components |
|---|
| 54 | | alias_method_chain :set_session_options, :components |
|---|
| 55 | | alias_method_chain :flash, :components |
|---|
| 56 | | |
|---|
| 57 | | alias_method :component_request?, :parent_controller |
|---|
| 58 | | end |
|---|
| 59 | | end |
|---|
| 60 | | |
|---|
| 61 | | module ClassMethods |
|---|
| 62 | | # Track parent controller to identify component requests |
|---|
| 63 | | def process_with_components(request, response, parent_controller = nil) #:nodoc: |
|---|
| 64 | | controller = new |
|---|
| 65 | | controller.parent_controller = parent_controller |
|---|
| 66 | | controller.process(request, response) |
|---|
| 67 | | end |
|---|
| 68 | | end |
|---|
| 69 | | |
|---|
| 70 | | module InstanceMethods |
|---|
| 71 | | # Extracts the action_name from the request parameters and performs that action. |
|---|
| 72 | | def process_with_components(request, response, method = :perform_action, *arguments) #:nodoc: |
|---|
| 73 | | flash.discard if component_request? |
|---|
| 74 | | process_without_components(request, response, method, *arguments) |
|---|
| 75 | | end |
|---|
| 76 | | |
|---|
| 77 | | protected |
|---|
| 78 | | # Renders the component specified as the response for the current method |
|---|
| 79 | | def render_component(options) #:doc: |
|---|
| 80 | | component_logging(options) do |
|---|
| 81 | | render_for_text(component_response(options, true).body, response.headers["Status"]) |
|---|
| 82 | | end |
|---|
| 83 | | end |
|---|
| 84 | | |
|---|
| 85 | | # Returns the component response as a string |
|---|
| 86 | | def render_component_as_string(options) #:doc: |
|---|
| 87 | | component_logging(options) do |
|---|
| 88 | | response = component_response(options, false) |
|---|
| 89 | | |
|---|
| 90 | | if redirected = response.redirected_to |
|---|
| 91 | | render_component_as_string(redirected) |
|---|
| 92 | | else |
|---|
| 93 | | response.body |
|---|
| 94 | | end |
|---|
| 95 | | end |
|---|
| 96 | | end |
|---|
| 97 | | |
|---|
| 98 | | def flash_with_components(refresh = false) #:nodoc: |
|---|
| 99 | | if !defined?(@_flash) || refresh |
|---|
| 100 | | @_flash = |
|---|
| 101 | | if defined?(@parent_controller) |
|---|
| 102 | | @parent_controller.flash |
|---|
| 103 | | else |
|---|
| 104 | | flash_without_components |
|---|
| 105 | | end |
|---|
| 106 | | end |
|---|
| 107 | | @_flash |
|---|
| 108 | | end |
|---|
| 109 | | |
|---|
| 110 | | private |
|---|
| 111 | | def component_response(options, reuse_response) |
|---|
| 112 | | klass = component_class(options) |
|---|
| 113 | | request = request_for_component(klass.controller_name, options) |
|---|
| 114 | | new_response = reuse_response ? response : response.dup |
|---|
| 115 | | |
|---|
| 116 | | klass.process_with_components(request, new_response, self) |
|---|
| 117 | | end |
|---|
| 118 | | |
|---|
| 119 | | # determine the controller class for the component request |
|---|
| 120 | | def component_class(options) |
|---|
| 121 | | if controller = options[:controller] |
|---|
| 122 | | controller.is_a?(Class) ? controller : "#{controller.camelize}Controller".constantize |
|---|
| 123 | | else |
|---|
| 124 | | self.class |
|---|
| 125 | | end |
|---|
| 126 | | end |
|---|
| 127 | | |
|---|
| 128 | | # Create a new request object based on the current request. |
|---|
| 129 | | # The new request inherits the session from the current request, |
|---|
| 130 | | # bypassing any session options set for the component controller's class |
|---|
| 131 | | def request_for_component(controller_name, options) |
|---|
| 132 | | new_request = request.dup |
|---|
| 133 | | new_request.session = request.session |
|---|
| 134 | | |
|---|
| 135 | | new_request.instance_variable_set( |
|---|
| 136 | | :@parameters, |
|---|
| 137 | | (options[:params] || {}).with_indifferent_access.update( |
|---|
| 138 | | "controller" => controller_name, "action" => options[:action], "id" => options[:id] |
|---|
| 139 | | ) |
|---|
| 140 | | ) |
|---|
| 141 | | |
|---|
| 142 | | new_request |
|---|
| 143 | | end |
|---|
| 144 | | |
|---|
| 145 | | def component_logging(options) |
|---|
| 146 | | if logger |
|---|
| 147 | | logger.info "Start rendering component (#{options.inspect}): " |
|---|
| 148 | | result = yield |
|---|
| 149 | | logger.info "\n\nEnd of component rendering" |
|---|
| 150 | | result |
|---|
| 151 | | else |
|---|
| 152 | | yield |
|---|
| 153 | | end |
|---|
| 154 | | end |
|---|
| 155 | | |
|---|
| 156 | | def set_session_options_with_components(request) |
|---|
| 157 | | set_session_options_without_components(request) unless component_request? |
|---|
| 158 | | end |
|---|
| 159 | | |
|---|
| 160 | | def process_cleanup_with_components |
|---|
| 161 | | process_cleanup_without_components unless component_request? |
|---|
| 162 | | end |
|---|
| 163 | | end |
|---|
| 164 | | end |
|---|
| 165 | | end |