Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Deprecated all render_* methods in favor of consolidating all renderi…

…ng behavior in Base#render(options). This enables more natural use of combining options, such as layouts

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@1350 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
commit da0c4c5c9695e2ebe8d98b391d901b598dd293a2 1 parent 0367317
@dhh dhh authored
View
14 actionpack/CHANGELOG
@@ -1,5 +1,19 @@
*SVN*
+* Deprecated all render_* methods in favor of consolidating all rendering behavior in Base#render(options). This enables more natural use of combining options, such as layouts. Examples:
+
+ +---------------------------------------------------------------+-------------------------------------------------------+
+ | BEFORE | AFTER |
+ +---------------------------------------------------------------+-------------------------------------------------------+
+ | render_with_layout "weblog/show", "200 OK", "layouts/dialog" | render :action => "show", :layout => "dialog" |
+ | render_without_layout "weblog/show" | render :action => "show", :layout => false |
+ | render_action "error", "404 Not Found" | render :action => "error", :status => "404 Not Found" |
+ | render_template "xml.div('stuff')", "200 OK", :rxml | render :inline => "xml.div('stuff')", :type => :rxml |
+ | render_text "hello world!" | render :text => "hello world!" |
+ | render_partial_collection "person", @people, nil, :a => 1 | render :partial => "person", :collection => @people, |
+ | | :locals => { :a => 1 } |
+ +---------------------------------------------------------------+-------------------------------------------------------+
+
* Deprecated redirect_to_path and redirect_to_url in favor of letting redirect_to do the right thing when passed either a path or url.
* Fixed use of an integer as return code for renders, so render_text "hello world", 404 now works #1327
View
3  actionpack/lib/action_controller.rb
@@ -32,6 +32,7 @@
end
require 'action_controller/base'
+require 'action_controller/deprecated_renders_and_redirects'
require 'action_controller/rescue'
require 'action_controller/benchmarking'
require 'action_controller/filters'
@@ -47,6 +48,7 @@
require 'action_controller/caching'
require 'action_controller/components'
require 'action_controller/verification'
+require 'action_controller/streaming'
require 'action_view'
ActionController::Base.template_class = ActionView::Base
@@ -66,4 +68,5 @@
include ActionController::Caching
include ActionController::Components
include ActionController::Verification
+ include ActionController::Streaming
end
View
261 actionpack/lib/action_controller/base.rb
@@ -205,13 +205,6 @@ class Base
DEFAULT_RENDER_STATUS_CODE = "200 OK"
- DEFAULT_SEND_FILE_OPTIONS = {
- :type => 'application/octet-stream'.freeze,
- :disposition => 'attachment'.freeze,
- :stream => true,
- :buffer_size => 4096
- }.freeze
-
# Determines whether the view has access to controller internals @request, @response, @session, and @template.
# By default, it does.
@@view_controller_internals = true
@@ -439,236 +432,74 @@ def action_name
end
protected
+ # Renders the template specified by <tt>template_name</tt>, which defaults to the name of the current controller and action.
+ # So calling +render+ in WeblogController#show will attempt to render "#{template_root}/weblog/show.rhtml" or
+ # "#{template_root}/weblog/show.rxml" (in that order). The template_root is set on the ActionController::Base class and is
+ # shared by all controllers. It's also possible to pass a status code using the second parameter. This defaults to "200 OK",
+ # but can be changed, such as by calling <tt>render("weblog/error", "500 Error")</tt>.
+
# A unified replacement for the individual renders (work-in-progress).
- def r(options = {}, &block)
+ def render(options = {}, deprecated_status = nil)
raise DoubleRenderError, "Can only render or redirect once per action" if performed?
+
+ # Backwards compatibility
+ return render({ :template => options || default_template_name, :status => deprecated_status }) if !options.is_a?(Hash)
+
add_variables_to_assigns
options[:status] = (options[:status] || DEFAULT_RENDER_STATUS_CODE).to_s
if options[:text]
@response.headers["Status"] = options[:status]
- @response.body = block_given? ? block : options[:text]
+ @response.body = options[:text]
@performed_render = true
return options[:text]
elsif options[:file]
assert_existance_of_template_file(options[:file]) if options[:use_full_path]
logger.info("Rendering #{options[:file]} (#{options[:status]})") unless logger.nil?
- r(options.merge({ :text => @template.render_file(options[:file], options[:use_full_path])}))
+ render(options.merge({ :text => @template.render_file(options[:file], options[:use_full_path])}))
elsif options[:template]
- r(options.merge({ :file => options[:template], :use_full_path => true }))
+ render(options.merge({ :file => options[:template], :use_full_path => true }))
elsif options[:inline]
- r(options.merge({ :text => @template.render_template(options[:type] || :rhtml, options[:inline]) }))
+ render(options.merge({ :text => @template.render_template(options[:type] || :rhtml, options[:inline]) }))
elsif options[:action]
- r(options.merge({ :template => default_template_name(options[:action]) }))
+ render(options.merge({ :template => default_template_name(options[:action]) }))
elsif options[:partial] && options[:collection]
- r(options.merge({
+ render(options.merge({
:text => (
@template.render_partial_collection(
- options[:partial], options[:collection], options[:spacer_template], options[:local_assigns]
+ options[:partial], options[:collection], options[:spacer_template], options[:locals]
) || ''
)
}))
elsif options[:partial]
- r(options.merge({ :text => @template.render_partial(options[:partial], options[:object], options[:local_assigns]) }))
+ render(options.merge({ :text => @template.render_partial(options[:partial], options[:object], options[:locals]) }))
elsif options[:nothing]
- r(options.merge({ :text => "" }))
+ render(options.merge({ :text => "" }))
else
- r(options.merge({ :template => default_template_name }))
+ render(options.merge({ :template => default_template_name }))
end
end
-
- # Renders the template specified by <tt>template_name</tt>, which defaults to the name of the current controller and action.
- # So calling +render+ in WeblogController#show will attempt to render "#{template_root}/weblog/show.rhtml" or
- # "#{template_root}/weblog/show.rxml" (in that order). The template_root is set on the ActionController::Base class and is
- # shared by all controllers. It's also possible to pass a status code using the second parameter. This defaults to "200 OK",
- # but can be changed, such as by calling <tt>render("weblog/error", "500 Error")</tt>.
- def render(template_name = nil, status = nil) #:doc:
- render_file(template_name || default_template_name, status, true)
- end
-
- # Works like render, but instead of requiring a full template name, you can get by with specifying the action name. So calling
- # <tt>render_action "show_many"</tt> in WeblogController#display will render "#{template_root}/weblog/show_many.rhtml" or
- # "#{template_root}/weblog/show_many.rxml".
- def render_action(action_name, status = nil) #:doc:
- render(default_template_name(action_name), status)
- end
-
- # Works like render, but disregards the template_root and requires a full path to the template that needs to be rendered. Can be
- # used like <tt>render_file "/Users/david/Code/Ruby/template"</tt> to render "/Users/david/Code/Ruby/template.rhtml" or
- # "/Users/david/Code/Ruby/template.rxml".
- def render_file(template_path, status = nil, use_full_path = false) #:doc:
- assert_existance_of_template_file(template_path) if use_full_path
- logger.info("Rendering #{template_path} (#{status || DEFAULT_RENDER_STATUS_CODE})") unless logger.nil?
-
- add_variables_to_assigns
- render_text(@template.render_file(template_path, use_full_path), status)
- end
-
- # Renders the +template+ string, which is useful for rendering short templates you don't want to bother having a file for. So
- # you'd call <tt>render_template "Hello, <%= @user.name %>"</tt> to greet the current user. Or if you want to render as Builder
- # template, you could do <tt>render_template "xml.h1 @user.name", nil, "rxml"</tt>.
- def render_template(template, status = nil, type = "rhtml") #:doc:
- add_variables_to_assigns
- render_text(@template.render_template(type, template), status)
- end
-
- # Renders the +text+ string without parsing it through any template engine. Useful for rendering static information as it's
- # considerably faster than rendering through the template engine.
- # Use block for response body if provided (useful for deferred rendering or streaming output).
- def render_text(text = nil, status = nil, &block) #:doc:
- raise DoubleRenderError, "Can only render or redirect once per action" if performed?
- add_variables_to_assigns
- @response.headers["Status"] = (status || DEFAULT_RENDER_STATUS_CODE).to_s
- @response.body = block_given? ? block : text
- @performed_render = true
- end
-
- # Renders an empty response that can be used when the request is only interested in triggering an effect. Do note that good
- # HTTP manners mandate that you don't use GET requests to trigger data changes.
- def render_nothing(status = nil) #:doc:
- render_text "", status
- end
# Returns the result of the render as a string.
- def render_to_string(template_name = default_template_name) #:doc:
- add_variables_to_assigns
- @template.render_file(template_name)
+ def render_to_string(options) #:doc:
+ result = render(options)
+ erase_render_results
+ return result
end
-
+
# Clears the rendered results, allowing for another render or redirect to be performed.
def erase_render_results #:nodoc:
@response.body = nil
@performed_render = false
end
-
- # Renders the partial specified by <tt>partial_path</tt>, which by default is the name of the action itself. Example:
- #
- # class WeblogController < ActionController::Base
- # def show
- # render_partial # renders "weblog/_show.r(xml|html)"
- # end
- # end
- def render_partial(partial_path = default_template_name, object = nil, local_assigns = {}) #:doc:
- add_variables_to_assigns
- render_text(@template.render_partial(partial_path, object, local_assigns))
- end
-
- # Renders a collection of partials using <tt>partial_name</tt> to iterate over the +collection+.
- def render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = {})#:doc:
- add_variables_to_assigns
- render_text(@template.render_collection_of_partials(partial_name, collection, partial_spacer_template, local_assigns) || '')
- end
-
- # Sends the file by streaming it 4096 bytes at a time. This way the
- # whole file doesn't need to be read into memory at once. This makes
- # it feasible to send even large files.
- #
- # Be careful to sanitize the path parameter if it coming from a web
- # page. send_file(@params['path']) allows a malicious user to
- # download any file on your server.
- #
- # Options:
- # * <tt>:filename</tt> - suggests a filename for the browser to use.
- # Defaults to File.basename(path).
- # * <tt>:type</tt> - specifies an HTTP content type.
- # Defaults to 'application/octet-stream'.
- # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
- # Valid values are 'inline' and 'attachment' (default).
- # * <tt>:streaming</tt> - whether to send the file to the user agent as it is read (true)
- # or to read the entire file before sending (false). Defaults to true.
- # * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
- # Defaults to 4096.
- #
- # The default Content-Type and Content-Disposition headers are
- # set to download arbitrary binary files in as many browsers as
- # possible. IE versions 4, 5, 5.5, and 6 are all known to have
- # a variety of quirks (especially when downloading over SSL).
- #
- # Simple download:
- # send_file '/path/to.zip'
- #
- # Show a JPEG in browser:
- # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
- #
- # Read about the other Content-* HTTP headers if you'd like to
- # provide the user with more information (such as Content-Description).
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
- #
- # Also be aware that the document may be cached by proxies and browsers.
- # The Pragma and Cache-Control headers declare how the file may be cached
- # by intermediaries. They default to require clients to validate with
- # the server before releasing cached responses. See
- # http://www.mnot.net/cache_docs/ for an overview of web caching and
- # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
- # for the Cache-Control header spec.
- def send_file(path, options = {}) #:doc:
- raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
-
- options[:length] ||= File.size(path)
- options[:filename] ||= File.basename(path)
- send_file_headers! options
-
- @performed_render = false
-
- if options[:stream]
- render_text do
- logger.info "Streaming file #{path}" unless logger.nil?
- len = options[:buffer_size] || 4096
- File.open(path, 'rb') do |file|
- if $stdout.respond_to?(:syswrite)
- begin
- while true
- $stdout.syswrite file.sysread(len)
- end
- rescue EOFError
- end
- else
- while buf = file.read(len)
- $stdout.write buf
- end
- end
- end
- end
- else
- logger.info "Sending file #{path}" unless logger.nil?
- File.open(path, 'rb') { |file| render_text file.read }
- end
- end
-
- # Send binary data to the user as a file download. May set content type, apparent file name,
- # and specify whether to show data inline or download as an attachment.
- #
- # Options:
- # * <tt>:filename</tt> - Suggests a filename for the browser to use.
- # * <tt>:type</tt> - specifies an HTTP content type.
- # Defaults to 'application/octet-stream'.
- # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
- # Valid values are 'inline' and 'attachment' (default).
- #
- # Generic data download:
- # send_data buffer
- #
- # Download a dynamically-generated tarball:
- # send_data generate_tgz('dir'), :filename => 'dir.tgz'
- #
- # Display an image Active Record in the browser:
- # send_data image.data, :type => image.content_type, :disposition => 'inline'
- #
- # See +send_file+ for more information on HTTP Content-* headers and caching.
- def send_data(data, options = {}) #:doc:
- logger.info "Sending data #{options[:filename]}" unless logger.nil?
- send_file_headers! options.merge(:length => data.size)
- @performed_render = false
- render_text data
- end
def rewrite_options(options)
if defaults = default_url_options(options)
@@ -724,19 +555,6 @@ def redirect_to(options = {}, *parameters_for_method_reference) #:doc:
end
end
end
-
- # Deprecated in favor of calling redirect_to directly with the path.
- def redirect_to_path(path) #:doc:
- redirect_to(path)
- end
-
- # Deprecated in favor of calling redirect_to directly with the url. If the resource has moved permanently, it's possible to pass
- # true as the second parameter and the browser will get "301 Moved Permanently" instead of "302 Found". This can also be done through
- # just setting the headers["Status"] to "301 Moved Permanently" before using the redirect_to.
- def redirect_to_url(url, permanently = false) #:doc:
- headers["Status"] = "301 Moved Permanently" if permanently
- redirect_to(url)
- end
# Resets the session by clearing out all the objects stored within and initializing a new session object.
def reset_session #:doc:
@@ -745,11 +563,6 @@ def reset_session #:doc:
@response.session = @session
end
- # Deprecated cookie writer method
- def cookie(*options)
- @response.headers["cookie"] << CGI::Cookie.new(*options)
- end
-
private
def initialize_template_class(response)
begin
@@ -800,7 +613,8 @@ def performed?
def action_methods
@action_methods ||= (self.class.public_instance_methods - self.class.hidden_actions)
end
-
+
+
def add_variables_to_assigns
add_instance_variables_to_assigns
add_class_variables_to_assigns if view_controller_internals
@@ -828,6 +642,7 @@ def protected_instance_variables
end
end
+
def request_origin
"#{@request.remote_ip} at #{Time.now.to_s}"
end
@@ -835,6 +650,7 @@ def request_origin
def close_session
@session.close unless @session.nil? || Hash === @session
end
+
def template_exists?(template_name = default_template_name)
@template.file_exists?(template_name)
@@ -852,23 +668,6 @@ def assert_existance_of_template_file(template_name)
end
end
- def send_file_headers!(options)
- options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
- [:length, :type, :disposition].each do |arg|
- raise ArgumentError, ":#{arg} option required" if options[arg].nil?
- end
-
- disposition = options[:disposition].dup || 'attachment'
- disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
-
- @headers.update(
- 'Content-Length' => options[:length],
- 'Content-Type' => options[:type].strip, # fixes a problem with extra '\r' with some browsers
- 'Content-Disposition' => disposition,
- 'Content-Transfer-Encoding' => 'binary'
- );
- end
-
def default_template_name(default_action_name = action_name)
"#{self.class.controller_path}/#{default_action_name}"
end
View
6 actionpack/lib/action_controller/benchmarking.rb
@@ -15,12 +15,12 @@ def self.append_features(base)
}
end
- def render_with_benchmark(template_name = default_template_name, status = "200 OK")
+ def render_with_benchmark(options = {}, deprecated_status = nil)
if logger.nil?
- render_without_benchmark(template_name, status)
+ render_without_benchmark(options, deprecated_status)
else
db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
- @rendering_runtime = Benchmark::measure{ render_without_benchmark(template_name, status) }.real
+ @rendering_runtime = Benchmark::measure{ render_without_benchmark(options, deprecated_status) }.real
if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected?
@db_rt_before_render = db_runtime
View
22 actionpack/lib/action_controller/cookies.rb
@@ -3,17 +3,17 @@ module ActionController #:nodoc:
# the cookies being written is what will be sent out will the response. Cookies are read by value (so you won't get the cookie object
# itself back -- just the value it holds). Examples for writing:
#
- # cookies["user_name"] = "david" # => Will set a simple session cookie
- # cookies["login"] = { :value => "XJ-122", :expires => Time.now + 360} # => Will set a cookie that expires in 1 hour
+ # cookies[:user_name] = "david" # => Will set a simple session cookie
+ # cookies[:login] = { :value => "XJ-122", :expires => Time.now + 360} # => Will set a cookie that expires in 1 hour
#
# Examples for reading:
#
- # cookies["user_name"] # => "david"
+ # cookies[:user_name] # => "david"
# cookies.size # => 2
#
# Example for deleting:
#
- # cookies.delete "user_name"
+ # cookies.delete :user_name
#
# All the option symbols for setting cookies are:
#
@@ -24,10 +24,16 @@ module ActionController #:nodoc:
# * <tt>secure</tt> - whether this cookie is a secure cookie or not (default to false).
# Secure cookies are only transmitted to HTTPS servers.
module Cookies
- # Returns the cookie container, which operates as described above.
- def cookies
- CookieJar.new(self)
- end
+ protected
+ # Returns the cookie container, which operates as described above.
+ def cookies
+ CookieJar.new(self)
+ end
+
+ # Deprecated cookie writer method
+ def cookie(*options)
+ @response.headers["cookie"] << CGI::Cookie.new(*options)
+ end
end
class CookieJar < Hash #:nodoc:
View
68 actionpack/lib/action_controller/deprecated_renders_and_redirects.rb
@@ -0,0 +1,68 @@
+module ActionController
+ class Base
+ protected
+ # Works like render, but instead of requiring a full template name, you can get by with specifying the action name. So calling
+ # <tt>render_action "show_many"</tt> in WeblogController#display will render "#{template_root}/weblog/show_many.rhtml" or
+ # "#{template_root}/weblog/show_many.rxml".
+ def render_action(action_name, status = nil) #:doc:
+ render :action => action_name, :status => status
+ end
+
+ # Works like render, but disregards the template_root and requires a full path to the template that needs to be rendered. Can be
+ # used like <tt>render_file "/Users/david/Code/Ruby/template"</tt> to render "/Users/david/Code/Ruby/template.rhtml" or
+ # "/Users/david/Code/Ruby/template.rxml".
+ def render_file(template_path, status = nil, use_full_path = false) #:doc:
+ render :file => template_path, :status => status, :use_full_path => use_full_path
+ end
+
+ # Renders the +template+ string, which is useful for rendering short templates you don't want to bother having a file for. So
+ # you'd call <tt>render_template "Hello, <%= @user.name %>"</tt> to greet the current user. Or if you want to render as Builder
+ # template, you could do <tt>render_template "xml.h1 @user.name", nil, "rxml"</tt>.
+ def render_template(template, status = nil, type = "rhtml") #:doc:
+ render :inline => template, :status => status, :type => type
+ end
+
+ # Renders the +text+ string without parsing it through any template engine. Useful for rendering static information as it's
+ # considerably faster than rendering through the template engine.
+ # Use block for response body if provided (useful for deferred rendering or streaming output).
+ def render_text(text = nil, status = nil) #:doc:
+ render(:text => text, :status => status) { yield }
+ end
+
+ # Renders an empty response that can be used when the request is only interested in triggering an effect. Do note that good
+ # HTTP manners mandate that you don't use GET requests to trigger data changes.
+ def render_nothing(status = nil) #:doc:
+ render :nothing => true, :status => status
+ end
+
+ # Renders the partial specified by <tt>partial_path</tt>, which by default is the name of the action itself. Example:
+ #
+ # class WeblogController < ActionController::Base
+ # def show
+ # render_partial # renders "weblog/_show.r(xml|html)"
+ # end
+ # end
+ def render_partial(partial_path = default_template_name, object = nil, local_assigns = {}) #:doc:
+ render :partial => partial_path, :object => object, :locals => local_assigns
+ end
+
+ # Renders a collection of partials using <tt>partial_name</tt> to iterate over the +collection+.
+ def render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = {})#:doc:
+ render :partial => partial_name, :collection => collection, :spacer_template => partial_spacer_template, :locals => local_assigns
+ end
+
+
+ # Deprecated in favor of calling redirect_to directly with the path.
+ def redirect_to_path(path) #:doc:
+ redirect_to(path)
+ end
+
+ # Deprecated in favor of calling redirect_to directly with the url. If the resource has moved permanently, it's possible to pass
+ # true as the second parameter and the browser will get "301 Moved Permanently" instead of "302 Found". This can also be done through
+ # just setting the headers["Status"] to "301 Moved Permanently" before using the redirect_to.
+ def redirect_to_url(url, permanently = false) #:doc:
+ headers["Status"] = "301 Moved Permanently" if permanently
+ redirect_to(url)
+ end
+ end
+end
View
21 actionpack/lib/action_controller/layout.rb
@@ -6,9 +6,6 @@ def self.append_features(base)
alias_method :render_without_layout, :render
alias_method :render, :render_with_layout
- alias_method :r_without_layout, :r
- alias_method :r, :r_with_layout
-
class << self
alias_method :inherited_without_layout, :inherited
end
@@ -205,7 +202,7 @@ def active_layout(passed_layout = nil)
active_layout.include?("/") ? active_layout : "layouts/#{active_layout}" if active_layout
end
- def render_with_layout(template_name = default_template_name, status = nil, layout = nil) #:nodoc:
+ def xrender_with_layout(template_name = default_template_name, status = nil, layout = nil) #:nodoc:
if layout ||= active_layout and action_has_layout?
add_variables_to_assigns
logger.info("Rendering #{template_name} within #{layout}") unless logger.nil?
@@ -216,23 +213,25 @@ def render_with_layout(template_name = default_template_name, status = nil, layo
end
end
- def r_with_layout(options = {})
- if (layout = active_layout_for_r(options)) && options[:text]
+ def render_with_layout(options = {}, deprecated_status = nil, deprecated_layout = nil)
+ if (layout = active_layout_for_r(options, deprecated_layout)) && options[:text]
add_variables_to_assigns
logger.info("Rendering #{template_name} within #{layout}") unless logger.nil?
- @content_for_layout = r_without_layout(options)
+ @content_for_layout = render_without_layout(options)
add_variables_to_assigns
erase_render_results
- r_without_layout(options.merge({ :text => @template.render_file(layout, true)}))
+ render_without_layout(options.merge({ :text => @template.render_file(layout, true), :status => options[:status] || deprecated_status }))
else
- r_without_layout(options)
+ render_without_layout(options, deprecated_status)
end
end
private
- def active_layout_for_r(options = {})
+ def active_layout_for_r(options = {}, deprecated_layout = nil)
+ return deprecated_layout unless deprecated_layout.nil?
+
case options[:layout]
when FalseClass
nil
@@ -244,7 +243,7 @@ def active_layout_for_r(options = {})
end
def action_has_layout?
- conditions = self.class.layout_conditions
+ conditions = self.class.layout_conditions || {}
case
when conditions[:only]
conditions[:only].include?(action_name)
View
133 actionpack/lib/action_controller/streaming.rb
@@ -0,0 +1,133 @@
+module ActionController #:nodoc:
+ # Methods for sending files and streams to the browser instead of rendering.
+ module Streaming
+ DEFAULT_SEND_FILE_OPTIONS = {
+ :type => 'application/octet-stream'.freeze,
+ :disposition => 'attachment'.freeze,
+ :stream => true,
+ :buffer_size => 4096
+ }.freeze
+
+ protected
+ # Sends the file by streaming it 4096 bytes at a time. This way the
+ # whole file doesn't need to be read into memory at once. This makes
+ # it feasible to send even large files.
+ #
+ # Be careful to sanitize the path parameter if it coming from a web
+ # page. send_file(@params['path']) allows a malicious user to
+ # download any file on your server.
+ #
+ # Options:
+ # * <tt>:filename</tt> - suggests a filename for the browser to use.
+ # Defaults to File.basename(path).
+ # * <tt>:type</tt> - specifies an HTTP content type.
+ # Defaults to 'application/octet-stream'.
+ # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
+ # Valid values are 'inline' and 'attachment' (default).
+ # * <tt>:streaming</tt> - whether to send the file to the user agent as it is read (true)
+ # or to read the entire file before sending (false). Defaults to true.
+ # * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.
+ # Defaults to 4096.
+ #
+ # The default Content-Type and Content-Disposition headers are
+ # set to download arbitrary binary files in as many browsers as
+ # possible. IE versions 4, 5, 5.5, and 6 are all known to have
+ # a variety of quirks (especially when downloading over SSL).
+ #
+ # Simple download:
+ # send_file '/path/to.zip'
+ #
+ # Show a JPEG in browser:
+ # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'
+ #
+ # Read about the other Content-* HTTP headers if you'd like to
+ # provide the user with more information (such as Content-Description).
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
+ #
+ # Also be aware that the document may be cached by proxies and browsers.
+ # The Pragma and Cache-Control headers declare how the file may be cached
+ # by intermediaries. They default to require clients to validate with
+ # the server before releasing cached responses. See
+ # http://www.mnot.net/cache_docs/ for an overview of web caching and
+ # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
+ # for the Cache-Control header spec.
+ def send_file(path, options = {}) #:doc:
+ raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)
+
+ options[:length] ||= File.size(path)
+ options[:filename] ||= File.basename(path)
+ send_file_headers! options
+
+ @performed_render = false
+
+ if options[:stream]
+ render :text => Proc.new {
+ logger.info "Streaming file #{path}" unless logger.nil?
+ len = options[:buffer_size] || 4096
+ File.open(path, 'rb') do |file|
+ if $stdout.respond_to?(:syswrite)
+ begin
+ while true
+ $stdout.syswrite file.sysread(len)
+ end
+ rescue EOFError
+ end
+ else
+ while buf = file.read(len)
+ $stdout.write buf
+ end
+ end
+ end
+ }
+ else
+ logger.info "Sending file #{path}" unless logger.nil?
+ File.open(path, 'rb') { |file| render :text => file.read }
+ end
+ end
+
+ # Send binary data to the user as a file download. May set content type, apparent file name,
+ # and specify whether to show data inline or download as an attachment.
+ #
+ # Options:
+ # * <tt>:filename</tt> - Suggests a filename for the browser to use.
+ # * <tt>:type</tt> - specifies an HTTP content type.
+ # Defaults to 'application/octet-stream'.
+ # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.
+ # Valid values are 'inline' and 'attachment' (default).
+ #
+ # Generic data download:
+ # send_data buffer
+ #
+ # Download a dynamically-generated tarball:
+ # send_data generate_tgz('dir'), :filename => 'dir.tgz'
+ #
+ # Display an image Active Record in the browser:
+ # send_data image.data, :type => image.content_type, :disposition => 'inline'
+ #
+ # See +send_file+ for more information on HTTP Content-* headers and caching.
+ def send_data(data, options = {}) #:doc:
+ logger.info "Sending data #{options[:filename]}" unless logger.nil?
+ send_file_headers! options.merge(:length => data.size)
+ @performed_render = false
+ render :text => data
+ end
+
+ private
+ def send_file_headers!(options)
+ options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))
+ [:length, :type, :disposition].each do |arg|
+ raise ArgumentError, ":#{arg} option required" if options[arg].nil?
+ end
+
+ disposition = options[:disposition].dup || 'attachment'
+ disposition <<= %(; filename="#{options[:filename]}") if options[:filename]
+
+ @headers.update(
+ 'Content-Length' => options[:length],
+ 'Content-Type' => options[:type].strip, # fixes a problem with extra '\r' with some browsers
+ 'Content-Disposition' => disposition,
+ 'Content-Transfer-Encoding' => 'binary'
+ );
+ end
+ end
+end
View
26 actionpack/test/controller/new_render_test.rb
@@ -17,29 +17,29 @@ def hello_world
end
def render_hello_world
- r :template => "test/hello_world"
+ render :template => "test/hello_world"
end
def render_hello_world_from_variable
@person = "david"
- r :text => "hello #{@person}"
+ render :text => "hello #{@person}"
end
def render_action_hello_world
- r :action => "hello_world"
+ render :action => "hello_world"
end
def render_text_hello_world
- r :text => "hello world"
+ render :text => "hello world"
end
def render_custom_code
- r :text => "hello world", :status => "404 Moved"
+ render :text => "hello world", :status => "404 Moved"
end
def render_xml_hello
@name = "David"
- r :template => "test/hello"
+ render :template => "test/hello"
end
def greeting
@@ -47,24 +47,24 @@ def greeting
end
def layout_test
- r :action => "hello_world"
+ render :action => "hello_world"
end
def layout_test_with_different_layout
- r :action => "hello_world", :layout => "standard"
+ render :action => "hello_world", :layout => "standard"
end
def rendering_without_layout
- r :action => "hello_world", :layout => false
+ render :action => "hello_world", :layout => false
end
def builder_layout_test
- r :action => "hello"
+ render :action => "hello"
end
def partials_list
@customers = [ Customer.new("david"), Customer.new("mary") ]
- r :action => "list"
+ render :action => "list"
end
def partial_only
@@ -73,11 +73,11 @@ def partial_only
def hello_in_a_string
@customers = [ Customer.new("david"), Customer.new("mary") ]
- r :text => "How's there? #{render_to_string("test/list")}"
+ render :text => "How's there? #{render_to_string("test/list")}"
end
def accessing_params_in_template
- r :inline => "Hello: <%= params[:name] %>"
+ render :inline => "Hello: <%= params[:name] %>"
end
def rescue_action(e) raise end
Please sign in to comment.
Something went wrong with that request. Please try again.