|
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 |