Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

root/branches/1-2-stable/actionpack/lib/action_controller/components.rb

Revision 5632, 7.0 kB (checked in by bitsweat, 2 years ago)

Merge [5631] from trunk.

Line 
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.send :include, InstanceMethods
40       base.extend(ClassMethods)
41
42       base.helper do
43         def render_component(options)
44           @controller.send(:render_component_as_string, options)
45         end
46       end
47            
48       # If this controller was instantiated to process a component request,
49       # +parent_controller+ points to the instantiator of this controller.
50       base.send :attr_accessor, :parent_controller
51      
52       base.class_eval do
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
69       # Set the template root to be one directory behind the root dir of the controller. Examples:
70       #   /code/weblog/components/admin/users_controller.rb with Admin::UsersController
71       #     will use /code/weblog/components as template root
72       #     and find templates in /code/weblog/components/admin/users/
73       #
74       #   /code/weblog/components/admin/parties/users_controller.rb with Admin::Parties::UsersController
75       #     will also use /code/weblog/components as template root
76       #     and find templates in /code/weblog/components/admin/parties/users/
77       def uses_component_template_root
78         path_of_calling_controller = File.dirname(caller[1].split(/:\d+:/, 2).first)
79         path_of_controller_root    = path_of_calling_controller.sub(/#{Regexp.escape(File.dirname(controller_path))}$/, "")
80
81         self.template_root = path_of_controller_root
82       end
83
84       deprecate :uses_component_template_root => 'Components are deprecated and will be removed in Rails 2.0.'
85     end
86
87     module InstanceMethods
88       # Extracts the action_name from the request parameters and performs that action.
89       def process_with_components(request, response, method = :perform_action, *arguments) #:nodoc:
90         flash.discard if component_request?
91         process_without_components(request, response, method, *arguments)
92       end
93      
94       protected
95         # Renders the component specified as the response for the current method
96         def render_component(options) #:doc:
97           component_logging(options) do
98             render_text(component_response(options, true).body, response.headers["Status"])
99           end
100         end
101
102         # Returns the component response as a string
103         def render_component_as_string(options) #:doc:
104           component_logging(options) do
105             response = component_response(options, false)
106
107             if redirected = response.redirected_to
108               render_component_as_string(redirected)
109             else
110               response.body
111             end
112           end
113         end
114
115         def flash_with_components(refresh = false) #:nodoc:
116           if !defined?(@_flash) || refresh
117             @_flash =
118               if defined?(@parent_controller)
119                 @parent_controller.flash
120               else
121                 flash_without_components
122               end
123           end
124           @_flash
125         end
126
127       private
128         def component_response(options, reuse_response)
129           klass    = component_class(options)
130           request  = request_for_component(klass.controller_name, options)
131           new_response = reuse_response ? response : response.dup
132
133           klass.process_with_components(request, new_response, self)
134         end
135
136         # determine the controller class for the component request
137         def component_class(options)
138           if controller = options[:controller]
139             controller.is_a?(Class) ? controller : "#{controller.camelize}Controller".constantize
140           else
141             self.class
142           end
143         end
144
145         # Create a new request object based on the current request.
146         # The new request inherits the session from the current request,
147         # bypassing any session options set for the component controller's class
148         def request_for_component(controller_name, options)
149           new_request         = request.dup
150           new_request.session = request.session
151
152           new_request.instance_variable_set(
153             :@parameters,
154             (options[:params] || {}).with_indifferent_access.update(
155               "controller" => controller_name, "action" => options[:action], "id" => options[:id]
156             )
157           )
158
159           new_request
160         end
161
162         def component_logging(options)
163           if logger
164             logger.info "Start rendering component (#{options.inspect}): "
165             result = yield
166             logger.info "\n\nEnd of component rendering"
167             result
168           else
169             yield
170           end
171         end
172
173         def set_session_options_with_components(request)
174           set_session_options_without_components(request) unless component_request?
175         end
176
177         def process_cleanup_with_components
178           process_cleanup_without_components unless component_request?
179         end
180     end
181   end
182 end
Note: See TracBrowser for help on using the browser.