rails / rails

Ruby on Rails

This URL has Read+Write access

dhh (author)
Tue Nov 23 17:04:44 -0800 2004
db045dbb » dhh 2004-11-23 Initial 1 require 'action_controller/request'
2 require 'action_controller/response'
3 require 'action_controller/url_rewriter'
4 require 'action_controller/support/class_attribute_accessors'
5 require 'action_controller/support/class_inheritable_attributes'
6 require 'action_controller/support/inflector'
7
8 module ActionController #:nodoc:
9 class ActionControllerError < StandardError #:nodoc:
10 end
11 class SessionRestoreError < ActionControllerError #:nodoc:
12 end
13 class MissingTemplate < ActionControllerError #:nodoc:
14 end
15 class UnknownAction < ActionControllerError #:nodoc:
16 end
17
18 # Action Controllers are made up of one or more actions that performs its purpose and then either renders a template or
19 # redirects to another action. An action is defined as a public method on the controller, which will automatically be
20 # made accessible to the web-server through a mod_rewrite mapping. A sample controller could look like this:
21 #
22 # class GuestBookController < ActionController::Base
23 # def index
24 # @entries = Entry.find_all
25 # end
26 #
27 # def sign
28 # Entry.create(@params["entry"])
29 # redirect_to :action => "index"
30 # end
31 # end
32 #
33 # GuestBookController.template_root = "templates/"
34 # GuestBookController.process_cgi
35 #
36 # All actions assume that you want to render a template matching the name of the action at the end of the performance
37 # unless you tell it otherwise. The index action complies with this assumption, so after populating the @entries instance
38 # variable, the GuestBookController will render "templates/guestbook/index.rhtml".
39 #
40 # Unlike index, the sign action isn't interested in rendering a template. So after performing its main purpose (creating a
41 # new entry in the guest book), it sheds the rendering assumption and initiates a redirect instead. This redirect works by
42 # returning an external "302 Moved" HTTP response that takes the user to the index action.
43 #
44 # The index and sign represent the two basic action archetypes used in Action Controllers. Get-and-show and do-and-redirect.
45 # Most actions are variations of these themes.
46 #
47 # Also note that it's the final call to <tt>process_cgi</tt> that actually initiates the action performance. It will extract
48 # request and response objects from the CGI
49 #
50 # == Requests
51 #
52 # Requests are processed by the Action Controller framework by extracting the value of the "action" key in the request parameters.
53 # This value should hold the name of the action to be performed. Once the action has been identified, the remaining
54 # request parameters, the session (if one is available), and the full request with all the http headers are made available to
55 # the action through instance variables. Then the action is performed.
56 #
57 # The full request object is available in @request and is primarily used to query for http headers. These queries are made by
58 # accessing the environment hash, like this:
59 #
60 # def hello_ip
61 # location = @request.env["REMOTE_ADDRESS"]
62 # render_text "Hello stranger from #{location}"
63 # end
64 #
65 # == Parameters
66 #
67 # All request parameters whether they come from a GET or POST request, or from the URL, are available through the @params hash.
68 # So an action that was performed through /weblog/list?category=All&limit=5 will include { "category" => "All", "limit" => 5 }
69 # in @params.
70 #
71 # It's also possible to construct multi-dimensional parameter hashes by specifying keys using brackets, such as:
72 #
73 # <input type="text" name="post[name]" value="david">
74 # <input type="text" name="post[address]" value="hyacintvej">
75 #
76 # A request stemming from a form holding these inputs will include { "post" # => { "name" => "david", "address" => "hyacintvej" } }.
77 # If the address input had been named "post[address][street]", the @params would have included
78 # { "post" => { "address" => { "street" => "hyacintvej" } } }. There's no limit to the depth of the nesting.
79 #
80 # == Sessions
81 #
82 # Sessions allows you to store objects in memory between requests. This is useful for objects that are not yet ready to be persisted,
83 # such as a Signup object constructed in a multi-paged process, or objects that don't change much and are needed all the time, such
84 # as a User object for a system that requires login. The session should not be used, however, as a cache for objects where it's likely
85 # they could be changed unknowingly. It's usually too much work to keep it all synchronized -- something databases already excel at.
86 #
87 # You can place objects in the session by using the <tt>@session</tt> hash:
88 #
89 # @session["person"] = Person.authenticate(user_name, password)
90 #
91 # And retrieved again through the same hash:
92 #
93 # Hello #{@session["person"]}
94 #
95 # Any object can be placed in the session (as long as it can be Marshalled). But remember that 1000 active sessions each storing a
96 # 50kb object could lead to a 50MB memory overhead. In other words, think carefully about size and caching before resorting to the use
97 # of the session.
98 #
99 # == Responses
100 #
101 # Each action results in a response, which holds the headers and document to be sent to the user's browser. The actual response
102 # object is generated automatically through the use of renders and redirects, so it's normally nothing you'll need to be concerned about.
103 #
104 # == Renders
105 #
106 # Action Controller sends content to the user by using one of five rendering methods. The most versatile and common is the rendering
107 # of a template. Included in the Action Pack is the Action View, which enables rendering of ERb templates. It's automatically configured.
108 # The controller passes objects to the view by assigning instance variables:
109 #
110 # def show
111 # @post = Post.find(@params["id"])
112 # end
113 #
114 # Which are then automatically available to the view:
115 #
116 # Title: <%= @post.title %>
117 #
118 # You don't have to rely on the automated rendering. Especially actions that could result in the rendering of different templates will use
119 # the manual rendering methods:
120 #
121 # def search
122 # @results = Search.find(@params["query"])
123 # case @results
124 # when 0 then render "weblog/no_results"
125 # when 1 then render_action "show"
126 # when 2..10 then render_action "show_many"
127 # end
128 # end
129 #
130 # Read more about writing ERb and Builder templates in link:classes/ActionView/Base.html.
131 #
132 # == Redirects
133 #
134 # Redirecting is what actions that update the model do when they're done. The <tt>save_post</tt> method shouldn't be responsible for also
135 # showing the post once it's saved -- that's the job for <tt>show_post</tt>. So once <tt>save_post</tt> has completed its business, it'll
136 # redirect to <tt>show_post</tt>. All redirects are external, which means that when the user refreshes his browser, it's not going to save
137 # the post again, but rather just show it one more time.
138 #
139 # This sounds fairly simple, but the redirection is complicated by the quest for a phenomenon known as "pretty urls". Instead of accepting
140 # the dreadful beings that is "weblog_controller?action=show&post_id=5", Action Controller goes out of its way to represent the former as
141 # "/weblog/show/5". And this is even the simple case. As an example of a more advanced pretty url consider
142 # "/library/books/ISBN/0743536703/show", which can be mapped to books_controller?action=show&type=ISBN&id=0743536703.
143 #
144 # Redirects work by rewriting the URL of the current action. So if the show action was called by "/library/books/ISBN/0743536703/show",
145 # we can redirect to an edit action simply by doing <tt>redirect_to(:action => "edit")</tt>, which could throw the user to
146 # "/library/books/ISBN/0743536703/edit". Naturally, you'll need to setup the .htaccess (or other means of URL rewriting for the web server)
147 # to point to the proper controller and action in the first place, but once you have, it can be rewritten with ease.
148 #
149 # Let's consider a bunch of examples on how to go from "/library/books/ISBN/0743536703/edit" to somewhere else:
150 #
151 # redirect_to(:action => "show", :action_prefix => "XTC/123") =>
152 # "http://www.singlefile.com/library/books/XTC/123/show"
153 #
154 # redirect_to(:path_params => {"type" => "EXBC"}) =>
155 # "http://www.singlefile.com/library/books/EXBC/0743536703/show"
156 #
157 # redirect_to(:controller => "settings") =>
158 # "http://www.singlefile.com/library/settings/"
159 #
160 # For more examples of redirecting options, have a look at the unit test in test/controller/url_test.rb. It's very readable and will give
161 # you an excellent understanding of the different options and what they do.
162 #
163 # == Environments
164 #
165 # Action Controller works out of the box with CGI, FastCGI, and mod_ruby. CGI and mod_ruby controllers are triggered just the same using:
166 #
167 # WeblogController.process_cgi
168 #
169 # FastCGI controllers are triggered using:
170 #
171 # FCGI.each_cgi{ |cgi| WeblogController.process_cgi(cgi) }
172 class Base
173 include ClassInheritableAttributes
174
175 DEFAULT_RENDER_STATUS_CODE = "200 OK"
176
177 DEFAULT_SEND_FILE_OPTIONS = {
178 :type => 'application/octet_stream',
179 :disposition => 'attachment',
180 :stream => true,
181 :buffer_size => 4096
182 }
183
184
185 # Determines whether the view has access to controller internals @request, @response, @session, and @template.
186 # By default, it does.
187 @@view_controller_internals = true
188 cattr_accessor :view_controller_internals
189
190 # All requests are considered local by default, so everyone will be exposed to detailed debugging screens on errors.
191 # When the application is ready to go public, this should be set to false, and the protected method <tt>local_request?</tt>
192 # should instead be implemented in the controller to determine when debugging screens should be shown.
193 @@consider_all_requests_local = true
194 cattr_accessor :consider_all_requests_local
195
196 # When turned on (which is default), all dependencies are included using "load". This mean that any change is instant in cached
197 # environments like mod_ruby or FastCGI. When set to false, "require" is used, which is faster but requires server restart to
198 # be effective.
199 @@reload_dependencies = true
200 cattr_accessor :reload_dependencies
201
202 # Template root determines the base from which template references will be made. So a call to render("test/template")
203 # will be converted to "#{template_root}/test/template.rhtml".
204 cattr_accessor :template_root
205
206 # The logger is used for generating information on the action run-time (including benchmarking) if available.
207 # Can be set to nil for no logging. Compatible with both Ruby's own Logger and Log4r loggers.
208 cattr_accessor :logger
209
210 # Determines which template class should be used by ActionController.
211 cattr_accessor :template_class
212
213 # Turn on +ignore_missing_templates+ if you want to unit test actions without making the associated templates.
214 cattr_accessor :ignore_missing_templates
215
216 # Holds the request object that's primarily used to get environment variables through access like
217 # <tt>@request.env["REQUEST_URI"]</tt>.
218 attr_accessor :request
219
220 # Holds a hash of all the GET, POST, and Url parameters passed to the action. Accessed like <tt>@params["post_id"]</tt>
221 # to get the post_id. No type casts are made, so all values are returned as strings.
222 attr_accessor :params
223
224 # Holds the response object that's primarily used to set additional HTTP headers through access like
225 # <tt>@response.headers["Cache-Control"] = "no-cache"</tt>. Can also be used to access the final body HTML after a template
226 # has been rendered through @response.body -- useful for <tt>after_filter</tt>s that wants to manipulate the output,
227 # such as a OutputCompressionFilter.
228 attr_accessor :response
229
230 # Holds a hash of objects in the session. Accessed like <tt>@session["person"]</tt> to get the object tied to the "person"
231 # key. The session will hold any type of object as values, but the key should be a string.
232 attr_accessor :session
233
234 # Holds a hash of header names and values. Accessed like <tt>@headers["Cache-Control"]</tt> to get the value of the Cache-Control
235 # directive. Values should always be specified as strings.
236 attr_accessor :headers
237
238 # Holds a hash of cookie names and values. Accessed like <tt>@cookies["user_name"]</tt> to get the value of the user_name cookie.
239 # This hash is read-only. You set new cookies using the cookie method.
240 attr_accessor :cookies
241
242 # Holds the hash of variables that are passed on to the template class to be made available to the view. This hash
243 # is generated by taking a snapshot of all the instance variables in the current scope just before a template is rendered.
244 attr_accessor :assigns
245
246 class << self
247 # Factory for the standard create, process loop where the controller is discarded after processing.
248 def process(request, response) #:nodoc:
249 new.process(request, response)
250 end
251
252 # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
253 def controller_class_name
254 Inflector.demodulize(name)
255 end
256
257 # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat".
258 def controller_name
259 Inflector.underscore(controller_class_name.sub(/Controller/, ""))
260 end
261
262 # Loads the <tt>file_name</tt> if reload_dependencies is true or requires if it's false.
263 def require_or_load(file_name)
264 reload_dependencies ? load("#{file_name}.rb") : require(file_name)
265 end
266 end
267
268 public
269 # Extracts the action_name from the request parameters and performs that action.
270 def process(request, response) #:nodoc:
271 initialize_template_class(response)
272 assign_shortcuts(request, response)
273 initialize_current_url
274
275 log_processing unless logger.nil?
276 perform_action
277 close_session
278
279 return @response
280 end
281
282 # Returns an URL that has been rewritten according to the hash of +options+ (for doing a complete redirect, use redirect_to). The
283 # valid keys in options are specified below with an example going from "/library/books/ISBN/0743536703/show" (mapped to
284 # books_controller?action=show&type=ISBN&id=0743536703):
285 #
286 # .---> controller .--> action
287 # /library/books/ISBN/0743536703/show
288 # '------> '--------------> action_prefix
289 # controller_prefix
290 #
291 # * <tt>:controller_prefix</tt> - specifies the string before the controller name, which would be "/library" for the example.
292 # Called with "/shop" gives "/shop/books/ISBN/0743536703/show".
293 # * <tt>:controller</tt> - specifies a new controller and clears out everything after the controller name (including the action,
294 # the pre- and suffix, and all params), so called with "settings" gives "/library/settings/".
295 # * <tt>:action_prefix</tt> - specifies the string between the controller name and the action name, which would
296 # be "/ISBN/0743536703" for the example. Called with "/XTC/123/" gives "/library/books/XTC/123/show".
297 # * <tt>:action</tt> - specifies a new action, so called with "edit" gives "/library/books/ISBN/0743536703/edit"
298 # * <tt>:action_suffix</tt> - specifies the string after the action name, which would be empty for the example.
299 # Called with "/detailed" gives "/library/books/ISBN/0743536703/detailed".
300 # * <tt>:path_params</tt> - specifies a hash that contains keys mapping to the request parameter names. In the example,
301 # { "type" => "ISBN", "id" => "0743536703" } would be the path_params. It serves as another way of replacing part of
302 # the action_prefix or action_suffix. So passing { "type" => "XTC" } would give "/library/books/XTC/0743536703/show".
303 # * <tt>:id</tt> - shortcut where ":id => 5" can be used instead of specifying :path_params => { "id" => 5 }.
304 # Called with "123" gives "/library/books/ISBN/123/show".
305 # * <tt>:params</tt> - specifies a hash that represents the regular request parameters, such as { "cat" => 1,
306 # "origin" => "there"} that would give "?cat=1&origin=there". Called with { "temporary" => 1 } in the example would give
307 # "/library/books/ISBN/0743536703/show?temporary=1"
308 # * <tt>:anchor</tt> - specifies the anchor name to be appended to the path. Called with "x14" would give
309 # "/library/books/ISBN/0743536703/show#x14"
310 # * <tt>:only_path</tt> - if true, returns the absolute URL (omitting the protocol, host name, and port).
311 #
312 # Naturally, you can combine multiple options in a single redirect. Examples:
313 #
314 # redirect_to(:controller_prefix => "/shop", :controller => "settings")
315 # redirect_to(:action => "edit", :id => 3425)
316 # redirect_to(:action => "edit", :path_params => { "type" => "XTC" }, :params => { "temp" => 1})
317 # redirect_to(:action => "publish", :action_prefix => "/published", :anchor => "x14")
318 #
319 # Instead of passing an options hash, you can also pass a method reference in the form of a symbol. Consider this example:
320 #
321 # class WeblogController < ActionController::Base
322 # def update
323 # # do some update
324 # redirect_to :dashboard_url
325 # end
326 #
327 # protected
328 # def dashboard_url
329 # url_for :controller => (@project.active? ? "project" : "account"), :action => "dashboard"
330 # end
331 # end
332 def url_for(options = {}, *parameters_for_method_reference) #:doc:
333 case options
334 when String then options
335 when Symbol then send(options, *parameters_for_method_reference)
336 when Hash then @url.rewrite(rewrite_options(options))
337 end
338 end
339
340 def module_name
341 @params["module"]
342 end
343
344 # Converts the class name from something like "OneModule::TwoModule::NeatController" to "NeatController".
345 def controller_class_name
346 self.class.controller_class_name
347 end
348
349 # Converts the class name from something like "OneModule::TwoModule::NeatController" to "neat".
350 def controller_name
351 self.class.controller_name
352 end
353
354 # Returns the name of the action this controller is processing.
355 def action_name
356 @params["action"] || "index"
357 end
358
359 protected
360 # Renders the template specified by <tt>template_name</tt>, which defaults to the name of the current controller and action.
361 # So calling +render+ in WeblogController#show will attempt to render "#{template_root}/weblog/show.rhtml" or
362 # "#{template_root}/weblog/show.rxml" (in that order). The template_root is set on the ActionController::Base class and is
363 # shared by all controllers. It's also possible to pass a status code using the second parameter. This defaults to "200 OK",
364 # but can be changed, such as by calling <tt>render("weblog/error", "500 Error")</tt>.
365 def render(template_name = nil, status = nil) #:doc:
366 render_file(template_name || default_template_name, status, true)
367 end
368
369 # Works like render, but instead of requiring a full template name, you can get by with specifying the action name. So calling
370 # <tt>render_action "show_many"</tt> in WeblogController#display will render "#{template_root}/weblog/show_many.rhtml" or
371 # "#{template_root}/weblog/show_many.rxml".
372 def render_action(action_name, status = nil) #:doc:
373 render default_template_name(action_name), status
374 end
375
376 # Works like render, but disregards the template_root and requires a full path to the template that needs to be rendered. Can be
377 # used like <tt>render_file "/Users/david/Code/Ruby/template"</tt> to render "/Users/david/Code/Ruby/template.rhtml" or
378 # "/Users/david/Code/Ruby/template.rxml".
379 def render_file(template_path, status = nil, use_full_path = false) #:doc:
380 assert_existance_of_template_file(template_path) if use_full_path
381 logger.info("Rendering #{template_path} (#{status || DEFAULT_RENDER_STATUS_CODE})") unless logger.nil?
382
383 add_variables_to_assigns
384 render_text(@template.render_file(template_path, use_full_path), status)
385 end
386
387 # Renders the +template+ string, which is useful for rendering short templates you don't want to bother having a file for. So
388 # you'd call <tt>render_template "Hello, <%= @user.name %>"</tt> to greet the current user. Or if you want to render as Builder
389 # template, you could do <tt>render_template "xml.h1 @user.name", nil, "rxml"</tt>.
390 def render_template(template, status = nil, type = "rhtml") #:doc:
391 add_variables_to_assigns
392 render_text(@template.render_template(type, template), status)
393 end
394
395 # Renders the +text+ string without parsing it through any template engine. Useful for rendering static information as it's
396 # considerably faster than rendering through the template engine.
397 # Use block for response body if provided (useful for deferred rendering or streaming output).
398 def render_text(text = nil, status = nil, &block) #:doc:
399 add_variables_to_assigns
400 @response.headers["Status"] = status || DEFAULT_RENDER_STATUS_CODE
401 @response.body = block_given? ? block : text
402 @performed_render = true
403 end
404
405 # Sends the file by streaming it 4096 bytes at a time. This way the
406 # whole file doesn't need to be read into memory at once. This makes
407 # it feasible to send even large files.
408 #
409 # Be careful to sanitize the path parameter if it coming from a web
410 # page. send_file(@params['path']) allows a malicious user to
411 # download any file on your server.
412 #
413 # Options:
414 # * <tt>:filename</tt> - suggests a filename for the browser to use.
415 # Defaults to File.basename(path).
416 # * <tt>:type</tt> - specifies an HTTP content type.
417 # Defaults to 'application/octet-stream'.
418 # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
419 # Valid values are 'inline' and 'attachment' (default).
420 # * <tt>:streaming</tt> - whether to send the file to the user agent as it is read (true)
421 # or to read the entire file before sending (false). Defaults to true.
422 # * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
423 # Defaults to 4096.
424 #
425 # The default Content-Type and Content-Disposition headers are
426 # set to download arbitrary binary files in as many browsers as
427 # possible. IE versions 4, 5, 5.5, and 6 are all known to have
428 # a variety of quirks (especially when downloading over SSL).
429 #
430 # Simple download:
431 # send_file '/path/to.zip'
432 #
433 # Show a JPEG in browser:
434 # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
435 #
436 # Read about the other Content-* HTTP headers if you'd like to
437 # provide the user with more information (such as Content-Description).
438 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
439 #
440 # Also be aware that the document may be cached by proxies and browsers.
441 # The Pragma and Cache-Control headers declare how the file may be cached
442 # by intermediaries. They default to require clients to validate with
443 # the server before releasing cached responses. See
444 # http://www.mnot.net/cache_docs/ for an overview of web caching and
445 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
446 # for the Cache-Control header spec.
447 def send_file(path, options = {})
448 raise MissingFile unless File.file?(path) and File.readable?(path)
449
450 options[:length] ||= File.size(path)
451 options[:filename] ||= File.basename(path)
452 send_file_headers! options
453
454 if options[:stream]
455 render_text do
456 logger.info "Streaming file #{path}" unless logger.nil?
457 len = options[:buffer_size] || 4096
458 File.open(path, 'rb') do |file|
459 begin
460 while true
461 $stdout.syswrite file.sysread(len)
462 end
463 rescue EOFError
464 end
465 end
466 end
467 else
468 logger.info "Sending file #{path}" unless logger.nil?
469 File.open(path, 'rb') { |file| render_text file.read }
470 end
471 end
472
473 # Send binary data to the user as a file download. May set content type, apparent file name,
474 # and specify whether to show data inline or download as an attachment.
475 #
476 # Options:
477 # * <tt>:filename</tt> - Suggests a filename for the browser to use.
478 # * <tt>:type</tt> - specifies an HTTP content type.
479 # Defaults to 'application/octet-stream'.
480 # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
481 # Valid values are 'inline' and 'attachment' (default).
482 #
483 # Generic data download:
484 # send_data buffer
485 #
486 # Download a dynamically-generated tarball:
487 # send_data generate_tgz('dir'), :filename => 'dir.tgz'
488 #
489 # Display an image Active Record in the browser:
490 # send_data image.data, :type => image.content_type, :disposition => 'inline'
491 #
492 # See +send_file+ for more information on HTTP Content-* headers and caching.
493 def send_data(data, options = {})
494 logger.info "Sending data #{options[:filename]}" unless logger.nil?
495 send_file_headers! options.merge(:length => data.size)
496 render_text data
497 end
498
499 def rewrite_options(options)
500 if defaults = default_url_options(options)
501 defaults.merge(options)
502 else
503 options
504 end
505 end
506
507 # Overwrite to implement a number of default options that all url_for-based methods will use. The default options should come in
508 # the form of a hash, just like the one you would use for url_for directly. Example:
509 #
510 # def default_url_options(options)
511 # { :controller_prefix => @project.active? ? "projects/" : "accounts/" }
512 # end
513 #
514 # As you can infer from the example, this is mostly useful for situations where you want to centralize dynamic decisions about the
515 # urls as they stem from the business domain. Please note that any individual url_for call can always override the defaults set
516 # by this method.
517 def default_url_options(options) #:doc:
518 end
519
520 # Redirects the browser to an URL that has been rewritten according to the hash of +options+ using a "302 Moved" HTTP header.
521 # See url_for for a description of the valid options.
522 def redirect_to(options = {}, *parameters_for_method_reference) #:doc:
523 if parameters_for_method_reference.empty?
524 @response.redirected_to = options
525 redirect_to_url(url_for(options))
526 else
527 @response.redirected_to, @response.redirected_to_method_params = options, parameters_for_method_reference
528 redirect_to_url(url_for(options, *parameters_for_method_reference))
529 end
530 end
531
532 # Redirects the browser to the specified <tt>path</tt> within the current host (specified with a leading /). Used to sidestep
533 # the URL rewriting and go directly to a known path. Example: <tt>redirect_to_path "/images/screenshot.jpg"</tt>.
534 def redirect_to_path(path) #:doc:
535 redirect_to_url(@request.protocol + @request.host_with_port + path)
536 end
537
538 # Redirects the browser to the specified <tt>url</tt>. Used to redirect outside of the current application. Example:
539 # <tt>redirect_to_url "http://www.rubyonrails.org"</tt>.
540 def redirect_to_url(url) #:doc:
541 logger.info("Redirected to #{url}") unless logger.nil?
542 @response.redirect(url)
543 @performed_redirect = true
544 end
545
546 # Creates a new cookie that is sent along-side the next render or redirect command. API is the same as for CGI::Cookie.
547 # Examples:
548 #
549 # cookie("name", "value1", "value2", ...)
550 # cookie("name" => "name", "value" => "value")
551 # cookie('name' => 'name',
552 # 'value' => ['value1', 'value2', ...],
553 # 'path' => 'path', # optional
554 # 'domain' => 'domain', # optional
555 # 'expires' => Time.now, # optional
556 # 'secure' => true # optional
557 # )
558 def cookie(*options) #:doc:
559 @response.headers["cookie"] << CGI::Cookie.new(*options)
560 end
561
562 # Resets the session by clearsing out all the objects stored within and initializing a new session object.
563 def reset_session #:doc:
564 @request.reset_session
565 @session = @request.session
566 @response.session = @session
567 end
568
569 private
570 def initialize_template_class(response)
571 begin
572 response.template = template_class.new(template_root, {}, self)
573 rescue
574 raise "You must assign a template class through ActionController.template_class= before processing a request"
575 end
576
577 @performed_render = @performed_redirect = false
578 end
579
580 def assign_shortcuts(request, response)
581 @request, @params, @cookies = request, request.parameters, request.cookies
582
583 @response = response
584 @response.session = request.session
585
586 @session = @response.session
587 @template = @response.template
588 @assigns = @response.template.assigns
589 @headers = @response.headers
590 end
591
592 def initialize_current_url
593 @url = UrlRewriter.new(@request, controller_name, action_name)
594 end
595
596 def log_processing
597 logger.info "\n\nProcessing #{controller_class_name}\##{action_name} (for #{request_origin})"
598 logger.info " Parameters: #{@params.inspect}"
599 end
600
601 def perform_action
602 if action_methods.include?(action_name)
603 send(action_name)
604 render unless @performed_render || @performed_redirect
605 elsif template_exists? && template_public?
606 render
607 else
608 raise UnknownAction, "No action responded to #{action_name}", caller
609 end
610 end
611
612 def action_methods
613 action_controller_classes = self.class.ancestors.reject{ |a| [Object, Kernel].include?(a) }
614 action_controller_classes.inject([]) { |action_methods, klass| action_methods + klass.instance_methods(false) }
615 end
616
617 def add_variables_to_assigns
618 add_instance_variables_to_assigns
619 add_class_variables_to_assigns if view_controller_internals
620 end
621
622 def add_instance_variables_to_assigns
623 protected_variables_cache = protected_instance_variables
624 instance_variables.each do |var|
625 next if protected_variables_cache.include?(var)
626 @assigns[var[1..-1]] = instance_variable_get(var)
627 end
628 end
629
630 def add_class_variables_to_assigns
631 %w( template_root logger template_class ignore_missing_templates ).each do |cvar|
632 @assigns[cvar] = self.send(cvar)
633 end
634 end
635
636 def protected_instance_variables
637 if view_controller_internals
638 [ "@assigns", "@performed_redirect", "@performed_render" ]
639 else
640 [ "@assigns", "@performed_redirect", "@performed_render", "@request", "@response", "@session", "@cookies", "@template" ]
641 end
642 end
643
644 def request_origin
645 "#{@request.remote_ip} at #{Time.now.to_s}"
646 end
647
648 def close_session
649 @session.close unless @session.nil? || Hash === @session
650 end
651
652 def template_exists?(template_name = default_template_name)
653 @template.file_exists?(template_name)
654 end
655
656 def template_public?(template_name = default_template_name)
657 @template.file_public?(template_name)
658 end
659
660 def assert_existance_of_template_file(template_name)
661 unless template_exists?(template_name) || ignore_missing_templates
662 full_template_path = @template.send(:full_template_path, template_name, 'rhtml')
663 template_type = (template_name =~ /layouts/i) ? 'layout' : 'template'
664 raise(MissingTemplate, "Missing #{template_type} #{full_template_path}")
665 end
666 end
667
668 def send_file_headers!(options)
669 options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
670 [:length, :type, :disposition].each do |arg|
671 raise ArgumentError, ":#{arg} option required" if options[arg].nil?
672 end
673
674 disposition = options[:disposition] || 'attachment'
675 disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
676
677 @headers.update(
678 'Content-Length' => options[:length],
679 'Content-Type' => options[:type],
680 'Content-Disposition' => disposition,
681 'Content-Transfer-Encoding' => 'binary'
682 );
683 end
684
685 def default_template_name(default_action_name = action_name)
686 module_name ? "#{module_name}/#{controller_name}/#{default_action_name}" : "#{controller_name}/#{default_action_name}"
687 end
688 end
689 end