Changeset 1350
- Timestamp:
- 05/22/05 08:58:43 (3 years ago)
- Files:
-
- trunk/actionpack/CHANGELOG (modified) (1 diff)
- trunk/actionpack/lib/action_controller.rb (modified) (3 diffs)
- trunk/actionpack/lib/action_controller/base.rb (modified) (9 diffs)
- trunk/actionpack/lib/action_controller/benchmarking.rb (modified) (1 diff)
- trunk/actionpack/lib/action_controller/cookies.rb (modified) (2 diffs)
- trunk/actionpack/lib/action_controller/deprecated_renders_and_redirects.rb (added)
- trunk/actionpack/lib/action_controller/layout.rb (modified) (4 diffs)
- trunk/actionpack/lib/action_controller/streaming.rb (added)
- trunk/actionpack/test/controller/new_render_test.rb (modified) (3 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/actionpack/CHANGELOG
r1349 r1350 1 1 *SVN* 2 3 * 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: 4 5 +---------------------------------------------------------------+-------------------------------------------------------+ 6 | BEFORE | AFTER | 7 +---------------------------------------------------------------+-------------------------------------------------------+ 8 | render_with_layout "weblog/show", "200 OK", "layouts/dialog" | render :action => "show", :layout => "dialog" | 9 | render_without_layout "weblog/show" | render :action => "show", :layout => false | 10 | render_action "error", "404 Not Found" | render :action => "error", :status => "404 Not Found" | 11 | render_template "xml.div('stuff')", "200 OK", :rxml | render :inline => "xml.div('stuff')", :type => :rxml | 12 | render_text "hello world!" | render :text => "hello world!" | 13 | render_partial_collection "person", @people, nil, :a => 1 | render :partial => "person", :collection => @people, | 14 | | :locals => { :a => 1 } | 15 +---------------------------------------------------------------+-------------------------------------------------------+ 2 16 3 17 * 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. trunk/actionpack/lib/action_controller.rb
r1008 r1350 33 33 34 34 require 'action_controller/base' 35 require 'action_controller/deprecated_renders_and_redirects' 35 36 require 'action_controller/rescue' 36 37 require 'action_controller/benchmarking' … … 48 49 require 'action_controller/components' 49 50 require 'action_controller/verification' 51 require 'action_controller/streaming' 50 52 51 53 require 'action_view' … … 67 69 include ActionController::Components 68 70 include ActionController::Verification 71 include ActionController::Streaming 69 72 end trunk/actionpack/lib/action_controller/base.rb
r1349 r1350 206 206 DEFAULT_RENDER_STATUS_CODE = "200 OK" 207 207 208 DEFAULT_SEND_FILE_OPTIONS = {209 :type => 'application/octet-stream'.freeze,210 :disposition => 'attachment'.freeze,211 :stream => true,212 :buffer_size => 4096213 }.freeze214 215 208 # Determines whether the view has access to controller internals @request, @response, @session, and @template. 216 209 # By default, it does. … … 440 433 441 434 protected 442 # A unified replacement for the individual renders (work-in-progress).443 def r(options = {}, &block)444 raise DoubleRenderError, "Can only render or redirect once per action" if performed?445 add_variables_to_assigns446 options[:status] = (options[:status] || DEFAULT_RENDER_STATUS_CODE).to_s447 448 if options[:text]449 @response.headers["Status"] = options[:status]450 @response.body = block_given? ? block : options[:text]451 @performed_render = true452 return options[:text]453 454 elsif options[:file]455 assert_existance_of_template_file(options[:file]) if options[:use_full_path]456 logger.info("Rendering #{options[:file]} (#{options[:status]})") unless logger.nil?457 r(options.merge({ :text => @template.render_file(options[:file], options[:use_full_path])}))458 459 elsif options[:template]460 r(options.merge({ :file => options[:template], :use_full_path => true }))461 462 elsif options[:inline]463 r(options.merge({ :text => @template.render_template(options[:type] || :rhtml, options[:inline]) }))464 465 elsif options[:action]466 r(options.merge({ :template => default_template_name(options[:action]) }))467 468 elsif options[:partial] && options[:collection]469 r(options.merge({470 :text => (471 @template.render_partial_collection(472 options[:partial], options[:collection], options[:spacer_template], options[:local_assigns]473 ) || ''474 )475 }))476 477 elsif options[:partial]478 r(options.merge({ :text => @template.render_partial(options[:partial], options[:object], options[:local_assigns]) }))479 480 elsif options[:nothing]481 r(options.merge({ :text => "" }))482 483 else484 r(options.merge({ :template => default_template_name }))485 end486 end487 488 435 # Renders the template specified by <tt>template_name</tt>, which defaults to the name of the current controller and action. 489 436 # So calling +render+ in WeblogController#show will attempt to render "#{template_root}/weblog/show.rhtml" or … … 491 438 # shared by all controllers. It's also possible to pass a status code using the second parameter. This defaults to "200 OK", 492 439 # but can be changed, such as by calling <tt>render("weblog/error", "500 Error")</tt>. 493 def render(template_name = nil, status = nil) #:doc: 494 render_file(template_name || default_template_name, status, true) 495 end 496 497 # Works like render, but instead of requiring a full template name, you can get by with specifying the action name. So calling 498 # <tt>render_action "show_many"</tt> in WeblogController#display will render "#{template_root}/weblog/show_many.rhtml" or 499 # "#{template_root}/weblog/show_many.rxml". 500 def render_action(action_name, status = nil) #:doc: 501 render(default_template_name(action_name), status) 502 end 503 504 # Works like render, but disregards the template_root and requires a full path to the template that needs to be rendered. Can be 505 # used like <tt>render_file "/Users/david/Code/Ruby/template"</tt> to render "/Users/david/Code/Ruby/template.rhtml" or 506 # "/Users/david/Code/Ruby/template.rxml". 507 def render_file(template_path, status = nil, use_full_path = false) #:doc: 508 assert_existance_of_template_file(template_path) if use_full_path 509 logger.info("Rendering #{template_path} (#{status || DEFAULT_RENDER_STATUS_CODE})") unless logger.nil? 440 441 # A unified replacement for the individual renders (work-in-progress). 442 def render(options = {}, deprecated_status = nil) 443 raise DoubleRenderError, "Can only render or redirect once per action" if performed? 444 445 # Backwards compatibility 446 return render({ :template => options || default_template_name, :status => deprecated_status }) if !options.is_a?(Hash) 510 447 511 448 add_variables_to_assigns 512 render_text(@template.render_file(template_path, use_full_path), status) 513 end 514 515 # Renders the +template+ string, which is useful for rendering short templates you don't want to bother having a file for. So 516 # you'd call <tt>render_template "Hello, <%= @user.name %>"</tt> to greet the current user. Or if you want to render as Builder 517 # template, you could do <tt>render_template "xml.h1 @user.name", nil, "rxml"</tt>. 518 def render_template(template, status = nil, type = "rhtml") #:doc: 519 add_variables_to_assigns 520 render_text(@template.render_template(type, template), status) 521 end 522 523 # Renders the +text+ string without parsing it through any template engine. Useful for rendering static information as it's 524 # considerably faster than rendering through the template engine. 525 # Use block for response body if provided (useful for deferred rendering or streaming output). 526 def render_text(text = nil, status = nil, &block) #:doc: 527 raise DoubleRenderError, "Can only render or redirect once per action" if performed? 528 add_variables_to_assigns 529 @response.headers["Status"] = (status || DEFAULT_RENDER_STATUS_CODE).to_s 530 @response.body = block_given? ? block : text 531 @performed_render = true 532 end 533 534 # Renders an empty response that can be used when the request is only interested in triggering an effect. Do note that good 535 # HTTP manners mandate that you don't use GET requests to trigger data changes. 536 def render_nothing(status = nil) #:doc: 537 render_text "", status 449 options[:status] = (options[:status] || DEFAULT_RENDER_STATUS_CODE).to_s 450 451 if options[:text] 452 @response.headers["Status"] = options[:status] 453 @response.body = options[:text] 454 @performed_render = true 455 return options[:text] 456 457 elsif options[:file] 458 assert_existance_of_template_file(options[:file]) if options[:use_full_path] 459 logger.info("Rendering #{options[:file]} (#{options[:status]})") unless logger.nil? 460 render(options.merge({ :text => @template.render_file(options[:file], options[:use_full_path])})) 461 462 elsif options[:template] 463 render(options.merge({ :file => options[:template], :use_full_path => true })) 464 465 elsif options[:inline] 466 render(options.merge({ :text => @template.render_template(options[:type] || :rhtml, options[:inline]) })) 467 468 elsif options[:action] 469 render(options.merge({ :template => default_template_name(options[:action]) })) 470 471 elsif options[:partial] && options[:collection] 472 render(options.merge({ 473 :text => ( 474 @template.render_partial_collection( 475 options[:partial], options[:collection], options[:spacer_template], options[:locals] 476 ) || '' 477 ) 478 })) 479 480 elsif options[:partial] 481 render(options.merge({ :text => @template.render_partial(options[:partial], options[:object], options[:locals]) })) 482 483 elsif options[:nothing] 484 render(options.merge({ :text => "" })) 485 486 else 487 render(options.merge({ :template => default_template_name })) 488 end 538 489 end 539 490 540 491 # Returns the result of the render as a string. 541 def render_to_string(template_name = default_template_name) #:doc: 542 add_variables_to_assigns 543 @template.render_file(template_name) 544 end 545 492 def render_to_string(options) #:doc: 493 result = render(options) 494 erase_render_results 495 return result 496 end 497 546 498 # Clears the rendered results, allowing for another render or redirect to be performed. 547 499 def erase_render_results #:nodoc: 548 500 @response.body = nil 549 501 @performed_render = false 550 end551 552 # Renders the partial specified by <tt>partial_path</tt>, which by default is the name of the action itself. Example:553 #554 # class WeblogController < ActionController::Base555 # def show556 # render_partial # renders "weblog/_show.r(xml|html)"557 # end558 # end559 def render_partial(partial_path = default_template_name, object = nil, local_assigns = {}) #:doc:560 add_variables_to_assigns561 render_text(@template.render_partial(partial_path, object, local_assigns))562 end563 564 # Renders a collection of partials using <tt>partial_name</tt> to iterate over the +collection+.565 def render_partial_collection(partial_name, collection, partial_spacer_template = nil, local_assigns = {})#:doc:566 add_variables_to_assigns567 render_text(@template.render_collection_of_partials(partial_name, collection, partial_spacer_template, local_assigns) || '')568 end569 570 # Sends the file by streaming it 4096 bytes at a time. This way the571 # whole file doesn't need to be read into memory at once. This makes572 # it feasible to send even large files.573 #574 # Be careful to sanitize the path parameter if it coming from a web575 # page. send_file(@params['path']) allows a malicious user to576 # download any file on your server.577 #578 # Options:579 # * <tt>:filename</tt> - suggests a filename for the browser to use.580 # Defaults to File.basename(path).581 # * <tt>:type</tt> - specifies an HTTP content type.582 # Defaults to 'application/octet-stream'.583 # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.584 # Valid values are 'inline' and 'attachment' (default).585 # * <tt>:streaming</tt> - whether to send the file to the user agent as it is read (true)586 # or to read the entire file before sending (false). Defaults to true.587 # * <tt>:buffer_size</tt> - specifies size (in bytes) of the buffer used to stream the file.588 # Defaults to 4096.589 #590 # The default Content-Type and Content-Disposition headers are591 # set to download arbitrary binary files in as many browsers as592 # possible. IE versions 4, 5, 5.5, and 6 are all known to have593 # a variety of quirks (especially when downloading over SSL).594 #595 # Simple download:596 # send_file '/path/to.zip'597 #598 # Show a JPEG in browser:599 # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline'600 #601 # Read about the other Content-* HTTP headers if you'd like to602 # provide the user with more information (such as Content-Description).603 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11604 #605 # Also be aware that the document may be cached by proxies and browsers.606 # The Pragma and Cache-Control headers declare how the file may be cached607 # by intermediaries. They default to require clients to validate with608 # the server before releasing cached responses. See609 # http://www.mnot.net/cache_docs/ for an overview of web caching and610 # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9611 # for the Cache-Control header spec.612 def send_file(path, options = {}) #:doc:613 raise MissingFile, "Cannot read file #{path}" unless File.file?(path) and File.readable?(path)614 615 options[:length] ||= File.size(path)616 options[:filename] ||= File.basename(path)617 send_file_headers! options618 619 @performed_render = false620 621 if options[:stream]622 render_text do623 logger.info "Streaming file #{path}" unless logger.nil?624 len = options[:buffer_size] || 4096625 File.open(path, 'rb') do |file|626 if $stdout.respond_to?(:syswrite)627 begin628 while true629 $stdout.syswrite file.sysread(len)630 end631 rescue EOFError632 end633 else634 while buf = file.read(len)635 $stdout.write buf636 end637 end638 end639 end640 else641 logger.info "Sending file #{path}" unless logger.nil?642 File.open(path, 'rb') { |file| render_text file.read }643 end644 end645 646 # Send binary data to the user as a file download. May set content type, apparent file name,647 # and specify whether to show data inline or download as an attachment.648 #649 # Options:650 # * <tt>:filename</tt> - Suggests a filename for the browser to use.651 # * <tt>:type</tt> - specifies an HTTP content type.652 # Defaults to 'application/octet-stream'.653 # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded.654 # Valid values are 'inline' and 'attachment' (default).655 #656 # Generic data download:657 # send_data buffer658 #659 # Download a dynamically-generated tarball:660 # send_data generate_tgz('dir'), :filename => 'dir.tgz'661 #662 # Display an image Active Record in the browser:663 # send_data image.data, :type => image.content_type, :disposition => 'inline'664 #665 # See +send_file+ for more information on HTTP Content-* headers and caching.666 def send_data(data, options = {}) #:doc:667 logger.info "Sending data #{options[:filename]}" unless logger.nil?668 send_file_headers! options.merge(:length => data.size)669 @performed_render = false670 render_text data671 502 end 672 503 … … 725 556 end 726 557 end 727 728 # Deprecated in favor of calling redirect_to directly with the path.729 def redirect_to_path(path) #:doc:730 redirect_to(path)731 end732 733 # Deprecated in favor of calling redirect_to directly with the url. If the resource has moved permanently, it's possible to pass734 # true as the second parameter and the browser will get "301 Moved Permanently" instead of "302 Found". This can also be done through735 # just setting the headers["Status"] to "301 Moved Permanently" before using the redirect_to.736 def redirect_to_url(url, permanently = false) #:doc:737 headers["Status"] = "301 Moved Permanently" if permanently738 redirect_to(url)739 end740 558 741 559 # Resets the session by clearing out all the objects stored within and initializing a new session object. … … 746 564 end 747 565 748 # Deprecated cookie writer method749 def cookie(*options)750 @response.headers["cookie"] << CGI::Cookie.new(*options)751 end752 753 566 private 754 567 def initialize_template_class(response) … … 801 614 @action_methods ||= (self.class.public_instance_methods - self.class.hidden_actions) 802 615 end 803 616 617 804 618 def add_variables_to_assigns 805 619 add_instance_variables_to_assigns … … 829 643 end 830 644 645 831 646 def request_origin 832 647 "#{@request.remote_ip} at #{Time.now.to_s}" … … 836 651 @session.close unless @session.nil? || Hash === @session 837 652 end 653 838 654 839 655 def template_exists?(template_name = default_template_name) … … 853 669 end 854 670 855 def send_file_headers!(options)856 options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options))857 [:length, :type, :disposition].each do |arg|858 raise ArgumentError, ":#{arg} option required" if options[arg].nil?859 end860 861 disposition = options[:disposition].dup || 'attachment'862 disposition <<= %(; filename="#{options[:filename]}") if options[:filename]863 864 @headers.update(865 'Content-Length' => options[:length],866 'Content-Type' => options[:type].strip, # fixes a problem with extra '\r' with some browsers867 'Content-Disposition' => disposition,868 'Content-Transfer-Encoding' => 'binary'869 );870 end871 872 671 def default_template_name(default_action_name = action_name) 873 672 "#{self.class.controller_path}/#{default_action_name}" trunk/actionpack/lib/action_controller/benchmarking.rb
r1222 r1350 16 16 end 17 17 18 def render_with_benchmark( template_name = default_template_name, status = "200 OK")18 def render_with_benchmark(options = {}, deprecated_status = nil) 19 19 if logger.nil? 20 render_without_benchmark( template_name,status)20 render_without_benchmark(options, deprecated_status) 21 21 else 22 22 db_runtime = ActiveRecord::Base.connection.reset_runtime if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? 23 @rendering_runtime = Benchmark::measure{ render_without_benchmark( template_name,status) }.real23 @rendering_runtime = Benchmark::measure{ render_without_benchmark(options, deprecated_status) }.real 24 24 25 25 if Object.const_defined?("ActiveRecord") && ActiveRecord::Base.connected? trunk/actionpack/lib/action_controller/cookies.rb
r1180 r1350 4 4 # itself back -- just the value it holds). Examples for writing: 5 5 # 6 # cookies[ "user_name"] = "david" # => Will set a simple session cookie7 # cookies[ "login"] = { :value => "XJ-122", :expires => Time.now + 360} # => Will set a cookie that expires in 1 hour6 # cookies[:user_name] = "david" # => Will set a simple session cookie 7 # cookies[:login] = { :value => "XJ-122", :expires => Time.now + 360} # => Will set a cookie that expires in 1 hour 8 8 # 9 9 # Examples for reading: 10 10 # 11 # cookies[ "user_name"] # => "david"11 # cookies[:user_name] # => "david" 12 12 # cookies.size # => 2 13 13 # 14 14 # Example for deleting: 15 15 # 16 # cookies.delete "user_name"16 # cookies.delete :user_name 17 17 # 18 18 # All the option symbols for setting cookies are: … … 25 25 # Secure cookies are only transmitted to HTTPS servers. 26 26 module Cookies 27 # Returns the cookie container, which operates as described above. 28 def cookies 29 CookieJar.new(self) 30 end 27 protected 28 # Returns the cookie container, which operates as described above. 29 def cookies 30 CookieJar.new(self) 31 end 32 33 # Deprecated cookie writer method 34 def cookie(*options) 35 @response.headers["cookie"] << CGI::Cookie.new(*options) 36 end 31 37 end 32 38 trunk/actionpack/lib/action_controller/layout.rb
r1349 r1350 6 6 alias_method :render_without_layout, :render 7 7 alias_method :render, :render_with_layout 8 9 alias_method :r_without_layout, :r10 alias_method :r, :r_with_layout11 8 12 9 class << self … … 206 203 end 207 204 208 def render_with_layout(template_name = default_template_name, status = nil, layout = nil) #:nodoc:205 def xrender_with_layout(template_name = default_template_name, status = nil, layout = nil) #:nodoc: 209 206 if layout ||= active_layout and action_has_layout? 210 207 add_variables_to_assigns … … 217 214 end 218 215 219 def r _with_layout(options = {})220 if (layout = active_layout_for_r(options )) && options[:text]216 def render_with_layout(options = {}, deprecated_status = nil, deprecated_layout = nil) 217 if (layout = active_layout_for_r(options, deprecated_layout)) && options[:text] 221 218 add_variables_to_assigns 222 219 logger.info("Rendering #{template_name} within #{layout}") unless logger.nil? 223 220 224 @content_for_layout = r _without_layout(options)221 @content_for_layout = render_without_layout(options) 225 222 add_variables_to_assigns 226 223 227 224 erase_render_results 228 r _without_layout(options.merge({ :text => @template.render_file(layout, true)}))225 render_without_layout(options.merge({ :text => @template.render_file(layout, true), :status => options[:status] || deprecated_status })) 229 226 else 230 r _without_layout(options)227 render_without_layout(options, deprecated_status) 231 228 end 232 229 end 233 230 234 231 private 235 def active_layout_for_r(options = {}) 232 def active_layout_for_r(options = {}, deprecated_layout = nil) 233 return deprecated_layout unless deprecated_layout.nil? 234 236 235 case options[:layout] 237 236 when FalseClass … … 245 244 246 245 def action_has_layout? 247 conditions = self.class.layout_conditions 246 conditions = self.class.layout_conditions || {} 248 247 case 249 248 when conditions[:only] trunk/actionpack/test/controller/new_render_test.rb
r1349 r1350 18 18 19 19 def render_hello_world 20 r :template => "test/hello_world"20 render :template => "test/hello_world" 21 21 end 22 22 23 23 def render_hello_world_from_variable 24 24 @person = "david" 25 r :text => "hello #{@person}"25 render :text => "hello #{@person}" 26 26 end 27 27 28 28 def render_action_hello_world 29 r :action => "hello_world"29 render :action => "hello_world" 30 30 end 31 31 32 32 def render_text_hello_world 33 r :text => "hello world"33 render :text => "hello world" 34 34 end 35 35 36 36 def render_custom_code 37 r :text => "hello world", :status => "404 Moved"37 render :text => "hello world", :status => "404 Moved" 38 38 end 39 39 40 40 def render_xml_hello 41 41 @name = "David" 42 r :template => "test/hello"42 render :template => "test/hello" 43 43 end 44 44 … … 48 48 49 49 def layout_test 50 r :action => "hello_world"50 render :action => "hello_world" 51 51 end 52 52 53 53 def layout_test_with_different_layout 54 r :action => "hello_world", :layout => "standard"54 render :action => "hello_world", :layout => "standard" 55 55 end 56 56 57 57 def rendering_without_layout 58 r :action => "hello_world", :layout => false58 render :action => "hello_world", :layout => false 59 59 end 60 60 61 61 def builder_layout_test 62 r :action => "hello"62 render :action => "hello" 63 63 end 64 64 65 65 def partials_list 66 66 @customers = [ Customer.new("david"), Customer.new("mary") ] 67 r :action => "list"67 render :action => "list" 68 68 end 69 69 … … 74 74 def hello_in_a_string 75 75 @customers = [ Customer.new("david"), Customer.new("mary") ] 76 r :text => "How's there? #{render_to_string("test/list")}"76 render :text => "How's there? #{render_to_string("test/list")}" 77 77 end 78 78 79 79 def accessing_params_in_template 80 r :inline => "Hello: <%= params[:name] %>"80 render :inline => "Hello: <%= params[:name] %>" 81 81 end 82 82