diff options
Diffstat (limited to 'actionpack')
68 files changed, 1340 insertions, 853 deletions
diff --git a/actionpack/lib/abstract_controller/helpers.rb b/actionpack/lib/abstract_controller/helpers.rb index 20f8601a8e..0ff1c0491a 100644 --- a/actionpack/lib/abstract_controller/helpers.rb +++ b/actionpack/lib/abstract_controller/helpers.rb @@ -112,17 +112,6 @@ module AbstractController default_helper_module! unless anonymous? end - private - # Makes all the (instance) methods in the helper module available to templates - # rendered through this controller. - # - # ==== Parameters - # * <tt>module</tt> - The module to include into the current helper module - # for the class - def add_template_helper(mod) - _helpers.module_eval { include mod } - end - # Returns a list of modules, normalized from the acceptable kinds of # helpers with the following behavior: # @@ -155,6 +144,17 @@ module AbstractController end end + private + # Makes all the (instance) methods in the helper module available to templates + # rendered through this controller. + # + # ==== Parameters + # * <tt>module</tt> - The module to include into the current helper module + # for the class + def add_template_helper(mod) + _helpers.module_eval { include mod } + end + def default_helper_module! module_name = name.sub(/Controller$/, '') module_path = module_name.underscore diff --git a/actionpack/lib/abstract_controller/rendering.rb b/actionpack/lib/abstract_controller/rendering.rb index ecd0c4fb73..306bd41e2d 100644 --- a/actionpack/lib/abstract_controller/rendering.rb +++ b/actionpack/lib/abstract_controller/rendering.rb @@ -105,16 +105,16 @@ module AbstractController # Normalize arguments, options and then delegates render_to_body and # sticks the result in self.response_body. def render(*args, &block) - self.response_body = render_to_string(*args, &block) + options = _normalize_render(*args, &block) + self.response_body = render_to_body(options) end # Raw rendering of a template to a string. Just convert the results of - # render_to_body into a String. + # render_response into a String. # :api: plugin def render_to_string(*args, &block) - options = _normalize_args(*args, &block) - _normalize_options(options) - render_to_body(options).tap { self.response_body = nil } + options = _normalize_render(*args, &block) + render_to_body(options) end # Raw rendering of a template to a Rack-compatible body. @@ -151,8 +151,17 @@ module AbstractController hash end - # Normalize options by converting render "foo" to render :action => "foo" and + # Normalize args and options. + # :api: private + def _normalize_render(*args, &block) + options = _normalize_args(*args, &block) + _normalize_options(options) + options + end + + # Normalize args by converting render "foo" to render :action => "foo" and # render "foo/bar" to render :file => "foo/bar". + # :api: plugin def _normalize_args(action=nil, options={}) case action when NilClass @@ -169,12 +178,14 @@ module AbstractController options end + # Normalize options. + # :api: plugin def _normalize_options(options) if options[:partial] == true options[:partial] = action_name end - if (options.keys & [:partial, :file, :template, :once]).empty? + if (options.keys & [:partial, :file, :template]).empty? options[:prefixes] ||= _prefixes end @@ -182,6 +193,8 @@ module AbstractController options end + # Process extra options. + # :api: plugin def _process_options(options) end end diff --git a/actionpack/lib/action_controller.rb b/actionpack/lib/action_controller.rb index 62cc18b253..aab2b9dc25 100644 --- a/actionpack/lib/action_controller.rb +++ b/actionpack/lib/action_controller.rb @@ -13,6 +13,7 @@ module ActionController autoload :Compatibility autoload :ConditionalGet autoload :Cookies + autoload :DataStreaming autoload :Flash autoload :ForceSSL autoload :Head diff --git a/actionpack/lib/action_controller/base.rb b/actionpack/lib/action_controller/base.rb index 5f9e082cd3..ca0dccf575 100644 --- a/actionpack/lib/action_controller/base.rb +++ b/actionpack/lib/action_controller/base.rb @@ -200,6 +200,7 @@ module ActionController RequestForgeryProtection, ForceSSL, Streaming, + DataStreaming, RecordIdentifier, HttpAuthentication::Basic::ControllerMethods, HttpAuthentication::Digest::ControllerMethods, diff --git a/actionpack/lib/action_controller/metal.rb b/actionpack/lib/action_controller/metal.rb index 585bd5e5ab..0133b2ecbc 100644 --- a/actionpack/lib/action_controller/metal.rb +++ b/actionpack/lib/action_controller/metal.rb @@ -131,7 +131,7 @@ module ActionController attr_internal :headers, :response, :request delegate :session, :to => "@_request" - def initialize(*) + def initialize @_headers = {"Content-Type" => "text/html"} @_status = 200 @_request = nil diff --git a/actionpack/lib/action_controller/metal/data_streaming.rb b/actionpack/lib/action_controller/metal/data_streaming.rb new file mode 100644 index 0000000000..997bc6e958 --- /dev/null +++ b/actionpack/lib/action_controller/metal/data_streaming.rb @@ -0,0 +1,145 @@ +require 'active_support/core_ext/file/path' + +module ActionController #:nodoc: + # Methods for sending arbitrary data and for streaming files to the browser, + # instead of rendering. + module DataStreaming + extend ActiveSupport::Concern + + include ActionController::Rendering + + DEFAULT_SEND_FILE_OPTIONS = { + :type => 'application/octet-stream'.freeze, + :disposition => 'attachment'.freeze, + }.freeze + + protected + # Sends the file. This uses a server-appropriate method (such as X-Sendfile) + # via the Rack::Sendfile middleware. The header to use is set via + # config.action_dispatch.x_sendfile_header, and defaults to "X-Sendfile". + # Your server can also configure this for you by setting the X-Sendfile-Type header. + # + # Be careful to sanitize the path parameter if it is coming from a web + # page. <tt>send_file(params[:path])</tt> 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 <tt>File.basename(path)</tt>. + # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify + # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json + # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded. + # Valid values are 'inline' and 'attachment' (default). + # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'. + # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from + # the URL, which is necessary for i18n filenames on certain browsers + # (setting <tt>:filename</tt> overrides this option). + # + # 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 the browser: + # + # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline' + # + # Show a 404 page in the browser: + # + # send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404 + # + # Read about the other Content-* HTTP headers if you'd like to + # provide the user with more information (such as Content-Description) in + # 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[:filename] ||= File.basename(path) unless options[:url_based_filename] + send_file_headers! options + + self.status = options[:status] || 200 + self.content_type = options[:content_type] if options.key?(:content_type) + self.response_body = File.open(path, "rb") + end + + # Sends the given binary data to the browser. This method is similar to + # <tt>render :text => data</tt>, but also allows you to specify whether + # the browser should display the response as a file attachment (i.e. in a + # download dialog) or as inline data. You may also set the content type, + # the apparent file name, and other things. + # + # 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'. You can specify + # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json + # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded. + # Valid values are 'inline' and 'attachment' (default). + # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'. + # + # 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: + send_file_headers! options.dup + render options.slice(:status, :content_type).merge(:text => data) + end + + private + def send_file_headers!(options) + options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options)) + [:type, :disposition].each do |arg| + raise ArgumentError, ":#{arg} option required" if options[arg].nil? + end + + disposition = options[:disposition] + disposition += %(; filename="#{options[:filename]}") if options[:filename] + + content_type = options[:type] + + if content_type.is_a?(Symbol) + extension = Mime[content_type] + raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension + self.content_type = extension + else + self.content_type = content_type + end + + headers.merge!( + 'Content-Disposition' => disposition, + 'Content-Transfer-Encoding' => 'binary' + ) + + response.sending_file = true + + # Fix a problem with IE 6.0 on opening downloaded files: + # If Cache-Control: no-cache is set (which Rails does by default), + # IE removes the file it just downloaded from its cache immediately + # after it displays the "open/save" dialog, which means that if you + # hit "open" the file isn't there anymore when the application that + # is called for handling the download is run, so let's workaround that + response.cache_control[:public] ||= false + end + end +end diff --git a/actionpack/lib/action_controller/metal/helpers.rb b/actionpack/lib/action_controller/metal/helpers.rb index 91a88ab68a..75757db564 100644 --- a/actionpack/lib/action_controller/metal/helpers.rb +++ b/actionpack/lib/action_controller/metal/helpers.rb @@ -76,35 +76,35 @@ module ActionController @helper_proxy ||= ActionView::Base.new.extend(_helpers) end - private - # Overwrite modules_for_helpers to accept :all as argument, which loads - # all helpers in helpers_path. - # - # ==== Parameters - # * <tt>args</tt> - A list of helpers - # - # ==== Returns - # * <tt>array</tt> - A normalized list of modules for the list of helpers provided. - def modules_for_helpers(args) - args += all_application_helpers if args.delete(:all) - super(args) - end + # Overwrite modules_for_helpers to accept :all as argument, which loads + # all helpers in helpers_path. + # + # ==== Parameters + # * <tt>args</tt> - A list of helpers + # + # ==== Returns + # * <tt>array</tt> - A normalized list of modules for the list of helpers provided. + def modules_for_helpers(args) + args += all_application_helpers if args.delete(:all) + super(args) + end - # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt> - def all_application_helpers - all_helpers_from_path(helpers_path) + def all_helpers_from_path(path) + helpers = [] + Array.wrap(path).each do |_path| + extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/ + helpers += Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') } end + helpers.sort! + helpers.uniq! + helpers + end - def all_helpers_from_path(path) - helpers = [] - Array.wrap(path).each do |_path| - extract = /^#{Regexp.quote(_path.to_s)}\/?(.*)_helper.rb$/ - helpers += Dir["#{_path}/**/*_helper.rb"].map { |file| file.sub(extract, '\1') } - end - helpers.sort! - helpers.uniq! - helpers - end + private + # Extract helper names from files in <tt>app/helpers/**/*_helper.rb</tt> + def all_application_helpers + all_helpers_from_path(helpers_path) + end end end end diff --git a/actionpack/lib/action_controller/metal/mime_responds.rb b/actionpack/lib/action_controller/metal/mime_responds.rb index 16d48e4677..f10287afb4 100644 --- a/actionpack/lib/action_controller/metal/mime_responds.rb +++ b/actionpack/lib/action_controller/metal/mime_responds.rb @@ -6,6 +6,8 @@ module ActionController #:nodoc: module MimeResponds extend ActiveSupport::Concern + include ActionController::ImplicitRender + included do class_attribute :responder, :mimes_for_respond_to self.responder = ActionController::Responder diff --git a/actionpack/lib/action_controller/metal/renderers.rb b/actionpack/lib/action_controller/metal/renderers.rb index dfda6618e7..0ad9dbeda9 100644 --- a/actionpack/lib/action_controller/metal/renderers.rb +++ b/actionpack/lib/action_controller/metal/renderers.rb @@ -95,17 +95,17 @@ module ActionController json = json.to_json(options) unless json.kind_of?(String) json = "#{options[:callback]}(#{json})" unless options[:callback].blank? self.content_type ||= Mime::JSON - self.response_body = json + json end add :js do |js, options| self.content_type ||= Mime::JS - self.response_body = js.respond_to?(:to_js) ? js.to_js(options) : js + js.respond_to?(:to_js) ? js.to_js(options) : js end add :xml do |xml, options| self.content_type ||= Mime::XML - self.response_body = xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml + xml.respond_to?(:to_xml) ? xml.to_xml(options) : xml end end end diff --git a/actionpack/lib/action_controller/metal/rendering.rb b/actionpack/lib/action_controller/metal/rendering.rb index 32d52c84c4..70fd79bb8b 100644 --- a/actionpack/lib/action_controller/metal/rendering.rb +++ b/actionpack/lib/action_controller/metal/rendering.rb @@ -18,6 +18,17 @@ module ActionController response_body end + # Overwrite render_to_string because body can now be set to a rack body. + def render_to_string(*) + if self.response_body = super + string = "" + response_body.each { |r| string << r } + string + end + ensure + self.response_body = nil + end + private # Normalize arguments by catching blocks and setting them on :update. diff --git a/actionpack/lib/action_controller/metal/streaming.rb b/actionpack/lib/action_controller/metal/streaming.rb index 312dc8eb3e..b9bd49f670 100644 --- a/actionpack/lib/action_controller/metal/streaming.rb +++ b/actionpack/lib/action_controller/metal/streaming.rb @@ -1,145 +1,61 @@ require 'active_support/core_ext/file/path' +require 'rack/chunked' module ActionController #:nodoc: - # Methods for sending arbitrary data and for streaming files to the browser, - # instead of rendering. + # Methods for sending streaming templates back to the client. module Streaming extend ActiveSupport::Concern - include ActionController::Rendering + include AbstractController::Rendering + attr_internal :stream - DEFAULT_SEND_FILE_OPTIONS = { - :type => 'application/octet-stream'.freeze, - :disposition => 'attachment'.freeze, - }.freeze - - protected - # Sends the file. This uses a server-appropriate method (such as X-Sendfile) - # via the Rack::Sendfile middleware. The header to use is set via - # config.action_dispatch.x_sendfile_header, and defaults to "X-Sendfile". - # Your server can also configure this for you by setting the X-Sendfile-Type header. - # - # Be careful to sanitize the path parameter if it is coming from a web - # page. <tt>send_file(params[:path])</tt> 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 <tt>File.basename(path)</tt>. - # * <tt>:type</tt> - specifies an HTTP content type. Defaults to 'application/octet-stream'. You can specify - # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json - # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded. - # Valid values are 'inline' and 'attachment' (default). - # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'. - # * <tt>:url_based_filename</tt> - set to +true+ if you want the browser guess the filename from - # the URL, which is necessary for i18n filenames on certain browsers - # (setting <tt>:filename</tt> overrides this option). - # - # 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 the browser: - # - # send_file '/path/to.jpeg', :type => 'image/jpeg', :disposition => 'inline' - # - # Show a 404 page in the browser: - # - # send_file '/path/to/404.html', :type => 'text/html; charset=utf-8', :status => 404 - # - # Read about the other Content-* HTTP headers if you'd like to - # provide the user with more information (such as Content-Description) in - # 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[:filename] ||= File.basename(path) unless options[:url_based_filename] - send_file_headers! options - - self.status = options[:status] || 200 - self.content_type = options[:content_type] if options.key?(:content_type) - self.response_body = File.open(path, "rb") - end - - # Sends the given binary data to the browser. This method is similar to - # <tt>render :text => data</tt>, but also allows you to specify whether - # the browser should display the response as a file attachment (i.e. in a - # download dialog) or as inline data. You may also set the content type, - # the apparent file name, and other things. - # - # 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'. You can specify - # either a string or a symbol for a registered type register with <tt>Mime::Type.register</tt>, for example :json - # * <tt>:disposition</tt> - specifies whether the file will be shown inline or downloaded. - # Valid values are 'inline' and 'attachment' (default). - # * <tt>:status</tt> - specifies the status code to send with the response. Defaults to '200 OK'. - # - # 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: - send_file_headers! options.dup - render options.slice(:status, :content_type).merge(:text => data) - end - - private - def send_file_headers!(options) - options.update(DEFAULT_SEND_FILE_OPTIONS.merge(options)) - [:type, :disposition].each do |arg| - raise ArgumentError, ":#{arg} option required" if options[arg].nil? + module ClassMethods + # Render streaming templates. It accepts :only, :except, :if and :unless as options + # to specify when to stream, as in ActionController filters. + def stream(options={}) + if defined?(Fiber) + before_filter :_stream_filter, options + else + raise "You cannot use streaming if Fiber is not available." end + end + end - disposition = options[:disposition] - disposition += %(; filename="#{options[:filename]}") if options[:filename] - - content_type = options[:type] + protected - if content_type.is_a?(Symbol) - extension = Mime[content_type] - raise ArgumentError, "Unknown MIME type #{options[:type]}" unless extension - self.content_type = extension + # Mark following render calls as streaming. + def _stream_filter #:nodoc: + self.stream = true + end + + # Consider the stream option when normalazing options. + def _normalize_options(options) #:nodoc: + super + options[:stream] = self.stream unless options.key?(:stream) + end + + # Set proper cache control and transfer encoding when streaming + def _process_options(options) #:nodoc: + super + if options[:stream] + if env["HTTP_VERSION"] == "HTTP/1.0" + options.delete(:stream) else - self.content_type = content_type + headers["Cache-Control"] ||= "no-cache" + headers["Transfer-Encoding"] = "chunked" + headers.delete("Content-Length") end - - headers.merge!( - 'Content-Disposition' => disposition, - 'Content-Transfer-Encoding' => 'binary' - ) - - response.sending_file = true - - # Fix a problem with IE 6.0 on opening downloaded files: - # If Cache-Control: no-cache is set (which Rails does by default), - # IE removes the file it just downloaded from its cache immediately - # after it displays the "open/save" dialog, which means that if you - # hit "open" the file isn't there anymore when the application that - # is called for handling the download is run, so let's workaround that - response.cache_control[:public] ||= false end + end + + # Call render_to_body if we are streaming instead of usual +render+. + def _render_template(options) #:nodoc: + if options.delete(:stream) + Rack::Chunked::Body.new view_context.render_body(options) + else + super + end + end end end +
\ No newline at end of file diff --git a/actionpack/lib/action_controller/railtie.rb b/actionpack/lib/action_controller/railtie.rb index 0dd13fa9bf..d2ba052c8d 100644 --- a/actionpack/lib/action_controller/railtie.rb +++ b/actionpack/lib/action_controller/railtie.rb @@ -4,6 +4,7 @@ require "action_dispatch/railtie" require "action_view/railtie" require "abstract_controller/railties/routes_helpers" require "action_controller/railties/paths" +require "sprockets/railtie" module ActionController class Railtie < Rails::Railtie @@ -21,7 +22,6 @@ module ActionController paths = app.config.paths options = app.config.action_controller - options.use_sprockets ||= app.config.assets.enabled options.assets_dir ||= paths["public"].first options.javascripts_dir ||= paths["public/javascripts"].first options.stylesheets_dir ||= paths["public/stylesheets"].first diff --git a/actionpack/lib/action_controller/railties/paths.rb b/actionpack/lib/action_controller/railties/paths.rb index dce3c2fe88..699c44c62c 100644 --- a/actionpack/lib/action_controller/railties/paths.rb +++ b/actionpack/lib/action_controller/railties/paths.rb @@ -16,14 +16,6 @@ module ActionController if klass.superclass == ActionController::Base && ActionController::Base.include_all_helpers klass.helper :all end - - if app.config.serve_static_assets && namespace - paths = namespace._railtie.config.paths - - klass.config.assets_dir = paths["public"].first - klass.config.javascripts_dir = paths["public/javascripts"].first - klass.config.stylesheets_dir = paths["public/stylesheets"].first - end end end end diff --git a/actionpack/lib/action_dispatch/http/response.rb b/actionpack/lib/action_dispatch/http/response.rb index 8e03a7879f..1f4f3ac0da 100644 --- a/actionpack/lib/action_dispatch/http/response.rb +++ b/actionpack/lib/action_dispatch/http/response.rb @@ -32,24 +32,35 @@ module ActionDispatch # :nodoc: # puts @response.body # end # end - class Response < Rack::Response - attr_accessor :request, :blank + class Response + attr_accessor :request, :header, :status + attr_writer :sending_file - attr_writer :header, :sending_file alias_method :headers=, :header= + alias_method :headers, :header + + delegate :[], :[]=, :to => :@header + delegate :each, :to => :@body + + # Sets the HTTP response's content MIME type. For example, in the controller + # you could write this: + # + # response.content_type = "text/plain" + # + # If a character set has been defined for this response (see charset=) then + # the character set information will also be included in the content type + # information. + attr_accessor :charset, :content_type + + CONTENT_TYPE = "Content-Type" + + cattr_accessor(:default_charset) { "utf-8" } module Setup def initialize(status = 200, header = {}, body = []) - @writer = lambda { |x| @body << x } - @block = nil - @length = 0 + self.body, self.header, self.status = body, header, status - @header = header - self.body, self.status = body, status - - @cookie = [] @sending_file = false - @blank = false if content_type = self["Content-Type"] @@ -62,6 +73,7 @@ module ActionDispatch # :nodoc: end end + include Rack::Response::Helpers include Setup include ActionDispatch::Http::Cache::Response @@ -106,13 +118,29 @@ module ActionDispatch # :nodoc: def body=(body) @blank = true if body == EMPTY - @body = body.respond_to?(:to_str) ? [body] : body + + # Explicitly check for strings. This is *wrong* theoretically + # but if we don't check this, the performance on string bodies + # is bad on Ruby 1.8 (because strings responds to each then). + @body = if body.respond_to?(:to_str) || !body.respond_to?(:each) + [body] + else + body + end end def body_parts @body end + def set_cookie(key, value) + ::Rack::Utils.set_cookie_header!(header, key, value) + end + + def delete_cookie(key, value={}) + ::Rack::Utils.delete_cookie_header!(header, key, value) + end + def location headers['Location'] end @@ -122,46 +150,21 @@ module ActionDispatch # :nodoc: headers['Location'] = url end - # Sets the HTTP response's content MIME type. For example, in the controller - # you could write this: - # - # response.content_type = "text/plain" - # - # If a character set has been defined for this response (see charset=) then - # the character set information will also be included in the content type - # information. - attr_accessor :charset, :content_type - - CONTENT_TYPE = "Content-Type" - - cattr_accessor(:default_charset) { "utf-8" } - def to_a assign_default_content_type_and_charset! handle_conditional_get! - self["Set-Cookie"] = self["Set-Cookie"].join("\n") if self["Set-Cookie"].respond_to?(:join) - super - end - alias prepare! to_a + @header["Set-Cookie"] = @header["Set-Cookie"].join("\n") if @header["Set-Cookie"].respond_to?(:join) - def each(&callback) - if @body.respond_to?(:call) - @writer = lambda { |x| callback.call(x) } - @body.call(self, self) + if [204, 304].include?(@status) + @header.delete "Content-Type" + [@status, @header, []] else - @body.each { |part| callback.call(part.to_s) } + [@status, @header, self] end - - @writer = callback - @block.call(self) if @block - end - - def write(str) - str = str.to_s - @writer.call str - str end + alias prepare! to_a + alias to_ary to_a # For implicit splat on 1.9.2 # Returns the response cookies, converted to a Hash of (name => value) pairs # @@ -180,18 +183,18 @@ module ActionDispatch # :nodoc: cookies end - private - def assign_default_content_type_and_charset! - return if headers[CONTENT_TYPE].present? + private - @content_type ||= Mime::HTML - @charset ||= self.class.default_charset + def assign_default_content_type_and_charset! + return if headers[CONTENT_TYPE].present? - type = @content_type.to_s.dup - type << "; charset=#{@charset}" unless @sending_file + @content_type ||= Mime::HTML + @charset ||= self.class.default_charset - headers[CONTENT_TYPE] = type - end + type = @content_type.to_s.dup + type << "; charset=#{@charset}" unless @sending_file + headers[CONTENT_TYPE] = type + end end end diff --git a/actionpack/lib/action_dispatch/middleware/flash.rb b/actionpack/lib/action_dispatch/middleware/flash.rb index 027ff7f8ac..2adbce031b 100644 --- a/actionpack/lib/action_dispatch/middleware/flash.rb +++ b/actionpack/lib/action_dispatch/middleware/flash.rb @@ -4,7 +4,7 @@ module ActionDispatch # read a notice you put there or <tt>flash["notice"] = "hello"</tt> # to put a new one. def flash - @env['action_dispatch.request.flash_hash'] ||= (session["flash"] || Flash::FlashHash.new) + @env[Flash::KEY] ||= (session["flash"] || Flash::FlashHash.new) end end @@ -40,18 +40,16 @@ module ActionDispatch # # See docs on the FlashHash class for more details about the flash. class Flash + KEY = 'action_dispatch.request.flash_hash'.freeze + class FlashNow #:nodoc: + attr_accessor :flash + def initialize(flash) @flash = flash - @closed = false end - attr_reader :closed - alias :closed? :closed - def close!; @closed = true end - def []=(k, v) - raise ClosedError, :flash if closed? @flash[k] = v @flash.discard(k) v @@ -79,11 +77,16 @@ module ActionDispatch @used = Set.new @closed = false @flashes = {} + @now = nil end - attr_reader :closed - alias :closed? :closed - def close!; @closed = true end + def initialize_copy(other) + if other.now_is_loaded? + @now = other.now.dup + @now.flash = self + end + super + end def []=(k, v) #:nodoc: raise ClosedError, :flash if closed? @@ -152,6 +155,10 @@ module ActionDispatch @now ||= FlashNow.new(self) end + attr_reader :closed + alias :closed? :closed + def close!; @closed = true; end + # Keeps either the entire current flash or a specific flash entry available for the next action: # # flash.keep # keeps the entire flash @@ -205,7 +212,12 @@ module ActionDispatch self[:notice] = message end - private + protected + + def now_is_loaded? + !!@now + end + # Used internally by the <tt>keep</tt> and <tt>discard</tt> methods # use() # marks the entire flash as used # use('msg') # marks the "msg" entry as used @@ -231,13 +243,18 @@ module ActionDispatch @app.call(env) ensure session = env['rack.session'] || {} - flash_hash = env['action_dispatch.request.flash_hash'] + flash_hash = env[KEY] if flash_hash - if !flash_hash.empty? || session.key?('flash') - session["flash"] = flash_hash - end - flash_hash.close! + if !flash_hash.empty? || session.key?('flash') + session["flash"] = flash_hash + new_hash = flash_hash.dup + else + new_hash = flash_hash + end + + env[KEY] = new_hash + new_hash.close! end if session.key?('flash') && session['flash'].empty? diff --git a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb index 64d3a87fd0..1a811ce1b1 100644 --- a/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb +++ b/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb @@ -29,7 +29,9 @@ module ActionDispatch end def generate_sid - ActiveSupport::SecureRandom.hex(16) + sid = ActiveSupport::SecureRandom.hex(16) + sid.encode!('UTF-8') if sid.respond_to?(:encode!) + sid end protected diff --git a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb index dbe3206808..b1adf3d2d1 100644 --- a/actionpack/lib/action_dispatch/middleware/show_exceptions.rb +++ b/actionpack/lib/action_dispatch/middleware/show_exceptions.rb @@ -50,7 +50,7 @@ module ActionDispatch # Only this middleware cares about RoutingError. So, let's just raise # it here. if headers['X-Cascade'] == 'pass' - raise ActionController::RoutingError, "No route matches #{env['PATH_INFO'].inspect}" + raise ActionController::RoutingError, "No route matches [#{env['REQUEST_METHOD']}] #{env['PATH_INFO'].inspect}" end rescue Exception => exception raise exception if env['action_dispatch.show_exceptions'] == false diff --git a/actionpack/lib/action_dispatch/middleware/static.rb b/actionpack/lib/action_dispatch/middleware/static.rb index c57f694c4d..348f7b86b8 100644 --- a/actionpack/lib/action_dispatch/middleware/static.rb +++ b/actionpack/lib/action_dispatch/middleware/static.rb @@ -2,25 +2,23 @@ require 'rack/utils' module ActionDispatch class FileHandler - def initialize(at, root) - @at, @root = at.chomp('/'), root.chomp('/') - @compiled_at = @at.blank? ? nil : /^#{Regexp.escape(at)}/ + def initialize(root) + @root = root.chomp('/') @compiled_root = /^#{Regexp.escape(root)}/ @file_server = ::Rack::File.new(@root) end def match?(path) path = path.dup - if !@compiled_at || path.sub!(@compiled_at, '') - full_path = path.empty? ? @root : File.join(@root, ::Rack::Utils.unescape(path)) - paths = "#{full_path}#{ext}" - matches = Dir[paths] - match = matches.detect { |m| File.file?(m) } - if match - match.sub!(@compiled_root, '') - match - end + full_path = path.empty? ? @root : File.join(@root, ::Rack::Utils.unescape(path)) + paths = "#{full_path}#{ext}" + + matches = Dir[paths] + match = matches.detect { |m| File.file?(m) } + if match + match.sub!(@compiled_root, '') + match end end @@ -39,9 +37,9 @@ module ActionDispatch class Static FILE_METHODS = %w(GET HEAD).freeze - def initialize(app, roots) + def initialize(app, path) @app = app - @file_handlers = create_file_handlers(roots) + @file_handler = FileHandler.new(path) end def call(env) @@ -49,24 +47,13 @@ module ActionDispatch method = env['REQUEST_METHOD'] if FILE_METHODS.include?(method) - @file_handlers.each do |file_handler| - if match = file_handler.match?(path) - env["PATH_INFO"] = match - return file_handler.call(env) - end + if match = @file_handler.match?(path) + env["PATH_INFO"] = match + return @file_handler.call(env) end end @app.call(env) end - - private - def create_file_handlers(roots) - roots = { '' => roots } unless roots.is_a?(Hash) - - roots.map do |at, root| - FileHandler.new(at, root) if File.exist?(root) - end.compact - end end end diff --git a/actionpack/lib/action_dispatch/routing/route_set.rb b/actionpack/lib/action_dispatch/routing/route_set.rb index b28f6c2297..1d09091dc7 100644 --- a/actionpack/lib/action_dispatch/routing/route_set.rb +++ b/actionpack/lib/action_dispatch/routing/route_set.rb @@ -275,8 +275,7 @@ module ActionDispatch module MountedHelpers end - def mounted_helpers(name = :main_app) - define_mounted_helper(name) if name + def mounted_helpers MountedHelpers end diff --git a/actionpack/lib/action_dispatch/testing/assertions/response.rb b/actionpack/lib/action_dispatch/testing/assertions/response.rb index 8a04cfa886..e209978fb7 100644 --- a/actionpack/lib/action_dispatch/testing/assertions/response.rb +++ b/actionpack/lib/action_dispatch/testing/assertions/response.rb @@ -42,7 +42,7 @@ module ActionDispatch elsif type.is_a?(Symbol) && @response.response_code == Rack::Utils::SYMBOL_TO_STATUS_CODE[type] assert_block("") { true } # to count the assertion else - assert_block(build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) { false } + assert(false, build_message(message, "Expected response to be a <?>, but was <?>", type, @response.response_code)) end end diff --git a/actionpack/lib/action_view.rb b/actionpack/lib/action_view.rb index 60665387b6..4547aceb28 100644 --- a/actionpack/lib/action_view.rb +++ b/actionpack/lib/action_view.rb @@ -44,6 +44,7 @@ module ActionView autoload :AbstractRenderer autoload :PartialRenderer autoload :TemplateRenderer + autoload :StreamingTemplateRenderer end autoload_at "action_view/template/resolver" do @@ -53,6 +54,16 @@ module ActionView autoload :FallbackFileSystemResolver end + autoload_at "action_view/buffers" do + autoload :OutputBuffer + autoload :StreamingBuffer + end + + autoload_at "action_view/flows" do + autoload :OutputFlow + autoload :StreamingFlow + end + autoload_at "action_view/template/error" do autoload :MissingTemplate autoload :ActionViewError diff --git a/actionpack/lib/action_view/base.rb b/actionpack/lib/action_view/base.rb index 7488127e35..87501d5b88 100644 --- a/actionpack/lib/action_view/base.rb +++ b/actionpack/lib/action_view/base.rb @@ -137,6 +137,12 @@ module ActionView #:nodoc: cattr_accessor :field_error_proc @@field_error_proc = Proc.new{ |html_tag, instance| "<div class=\"field_with_errors\">#{html_tag}</div>".html_safe } + # How to complete the streaming when an exception occurs. + # This is our best guess: first try to close the attribute, then the tag. + # Currently this is private API and may be changed at *any* time. + cattr_accessor :streaming_completion_on_exception + @@streaming_completion_on_exception = %("><script type="text/javascript">window.location = "/500.html"</script></html>) + class_attribute :helpers class_attribute :_routes @@ -153,7 +159,7 @@ module ActionView #:nodoc: end end - attr_accessor :_template + attr_accessor :_template, :_view_flow attr_internal :request, :controller, :config, :assigns, :lookup_context delegate :formats, :formats=, :locale, :locale=, :view_paths, :view_paths=, :to => :lookup_context @@ -181,8 +187,8 @@ module ActionView #:nodoc: self.helpers = Module.new unless self.class.helpers @_config = {} - @_content_for = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new } @_virtual_path = nil + @_view_flow = OutputFlow.new @output_buffer = nil if @_controller = controller @@ -195,10 +201,6 @@ module ActionView #:nodoc: @_lookup_context.formats = formats if formats end - def store_content_for(key, value) - @_content_for[key] = value - end - def controller_path @controller_path ||= controller && controller.controller_path end diff --git a/actionpack/lib/action_view/buffers.rb b/actionpack/lib/action_view/buffers.rb new file mode 100644 index 0000000000..089fc68706 --- /dev/null +++ b/actionpack/lib/action_view/buffers.rb @@ -0,0 +1,43 @@ +require 'active_support/core_ext/string/output_safety' + +module ActionView + class OutputBuffer < ActiveSupport::SafeBuffer #:nodoc: + def initialize(*) + super + encode! if encoding_aware? + end + + def <<(value) + super(value.to_s) + end + alias :append= :<< + alias :safe_append= :safe_concat + end + + class StreamingBuffer #:nodoc: + def initialize(block) + @block = block + end + + def <<(value) + value = value.to_s + value = ERB::Util.h(value) unless value.html_safe? + @block.call(value) + end + alias :concat :<< + alias :append= :<< + + def safe_concat(value) + @block.call(value.to_s) + end + alias :safe_append= :safe_concat + + def html_safe? + true + end + + def html_safe + self + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/context.rb b/actionpack/lib/action_view/context.rb index 39d88333e8..a2a64de206 100644 --- a/actionpack/lib/action_view/context.rb +++ b/actionpack/lib/action_view/context.rb @@ -2,6 +2,7 @@ module ActionView module CompiledTemplates #:nodoc: # holds compiled template code end + # = Action View Context # # Action View contexts are supplied to Action Controller to render template. diff --git a/actionpack/lib/action_view/flows.rb b/actionpack/lib/action_view/flows.rb new file mode 100644 index 0000000000..386a06511f --- /dev/null +++ b/actionpack/lib/action_view/flows.rb @@ -0,0 +1,79 @@ +require 'active_support/core_ext/string/output_safety' + +module ActionView + class OutputFlow #:nodoc: + attr_reader :content + + def initialize + @content = Hash.new { |h,k| h[k] = ActiveSupport::SafeBuffer.new } + end + + # Called by _layout_for to read stored values. + def get(key) + @content[key] + end + + # Called by each renderer object to set the layout contents. + def set(key, value) + @content[key] = value + end + + # Called by content_for + def append(key, value) + @content[key] << value + end + + # Called by provide + def append!(key, value) + @content[key] << value + end + end + + class StreamingFlow < OutputFlow #:nodoc: + def initialize(view, fiber) + @view = view + @parent = nil + @child = view.output_buffer + @content = view._view_flow.content + @fiber = fiber + @root = Fiber.current.object_id + end + + # Try to get an stored content. If the content + # is not available and we are inside the layout + # fiber, we set that we are waiting for the given + # key and yield. + def get(key) + return super if @content.key?(key) + + if inside_fiber? + view = @view + + begin + @waiting_for = key + view.output_buffer, @parent = @child, view.output_buffer + Fiber.yield + ensure + @waiting_for = nil + view.output_buffer, @child = @parent, view.output_buffer + end + end + + super + end + + # Appends the contents for the given key. This is called + # by provides and resumes back to the fiber if it is + # the key it is waiting for. + def append!(key, value) + super + @fiber.resume if @waiting_for == key + end + + private + + def inside_fiber? + Fiber.current.object_id != @root + end + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_paths.rb new file mode 100644 index 0000000000..55a4c442fd --- /dev/null +++ b/actionpack/lib/action_view/helpers/asset_paths.rb @@ -0,0 +1,80 @@ +require 'active_support/core_ext/file' +require 'action_view/helpers/asset_paths' + +module ActionView + module Helpers + + class AssetPaths #:nodoc: + attr_reader :config, :controller + + def initialize(config, controller) + @config = config + @controller = controller + end + + # Add the extension +ext+ if not present. Return full URLs otherwise untouched. + # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL + # roots. Rewrite the asset path for cache-busting asset ids. Include + # asset host, if configured, with the correct request protocol. + def compute_public_path(source, dir, ext = nil, include_host = true) + source = source.to_s + return source if is_uri?(source) + + source = rewrite_extension(source, dir, ext) if ext + source = "/#{dir}/#{source}" unless source[0] == ?/ + source = rewrite_asset_path(source, dir) + + if controller && include_host + has_request = controller.respond_to?(:request) + source = rewrite_host_and_protocol(source, has_request) + end + + source + end + + def is_uri?(path) + path =~ %r{^[-a-z]+://|^cid:} + end + + private + + def rewrite_extension(source, dir, ext) + raise NotImplementedError + end + + def rewrite_asset_path(source, path = nil) + raise NotImplementedError + end + + def rewrite_host_and_protocol(source, has_request) + host = compute_asset_host(source) + if has_request && host && !is_uri?(host) + host = "#{controller.request.protocol}#{host}" + end + "#{host}#{source}" + end + + # Pick an asset host for this source. Returns +nil+ if no host is set, + # the host if no wildcard is set, the host interpolated with the + # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4), + # or the value returned from invoking the proc if it's a proc or the value from + # invoking call if it's an object responding to call. + def compute_asset_host(source) + if host = config.asset_host + if host.is_a?(Proc) || host.respond_to?(:call) + case host.is_a?(Proc) ? host.arity : host.method(:call).arity + when 2 + request = controller.respond_to?(:request) && controller.request + host.call(source, request) + else + host.call(source) + end + else + (host =~ /%d/) ? host % (source.hash % 4) : host + end + end + end + end + + end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/asset_tag_helper.rb b/actionpack/lib/action_view/helpers/asset_tag_helper.rb index c383e4990c..9bc847a1ab 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb @@ -268,13 +268,17 @@ module ActionView # image_path("edit.png") # => "/images/edit.png" # image_path("icons/edit.png") # => "/images/icons/edit.png" # image_path("/icons/edit.png") # => "/icons/edit.png" - # image_path("http://www.railsapplication.com/img/edit.png") # => "http://www.railsapplication.com/img/edit.png" + # image_path("http://www.example.com/img/edit.png") # => "http://www.example.com/img/edit.png" # # If you have images as application resources this method may conflict with their named routes. # The alias +path_to_image+ is provided to avoid that. Rails uses the alias internally, and # plugin authors are encouraged to do so. def image_path(source) - asset_paths.compute_public_path(source, 'images') + if config.use_sprockets + asset_path(source) + else + asset_paths.compute_public_path(source, 'images') + end end alias_method :path_to_image, :image_path # aliased to avoid conflicts with an image_path named route @@ -287,9 +291,13 @@ module ActionView # video_path("hd.avi") # => /videos/hd.avi # video_path("trailers/hd.avi") # => /videos/trailers/hd.avi # video_path("/trailers/hd.avi") # => /trailers/hd.avi - # video_path("http://www.railsapplication.com/vid/hd.avi") # => http://www.railsapplication.com/vid/hd.avi + # video_path("http://www.example.com/vid/hd.avi") # => http://www.example.com/vid/hd.avi def video_path(source) - asset_paths.compute_public_path(source, 'videos') + if config.use_sprockets + asset_path(source) + else + asset_paths.compute_public_path(source, 'videos') + end end alias_method :path_to_video, :video_path # aliased to avoid conflicts with a video_path named route @@ -302,9 +310,13 @@ module ActionView # audio_path("horse.wav") # => /audios/horse.wav # audio_path("sounds/horse.wav") # => /audios/sounds/horse.wav # audio_path("/sounds/horse.wav") # => /sounds/horse.wav - # audio_path("http://www.railsapplication.com/sounds/horse.wav") # => http://www.railsapplication.com/sounds/horse.wav + # audio_path("http://www.example.com/sounds/horse.wav") # => http://www.example.com/sounds/horse.wav def audio_path(source) - asset_paths.compute_public_path(source, 'audios') + if config.use_sprockets + asset_path(source) + else + asset_paths.compute_public_path(source, 'audios') + end end alias_method :path_to_audio, :audio_path # aliased to avoid conflicts with an audio_path named route @@ -434,7 +446,7 @@ module ActionView private def asset_paths - @asset_paths ||= AssetPaths.new(config, controller) + @asset_paths ||= AssetTagHelper::AssetPaths.new(config, controller) end end end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb index 52eb43a1cd..e4662a2919 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb @@ -8,9 +8,11 @@ module ActionView module AssetTagHelper class AssetIncludeTag - attr_reader :config, :asset_paths + include TagHelper + attr_reader :config, :asset_paths class_attribute :expansions + def self.inherited(base) base.expansions = { } end @@ -56,7 +58,6 @@ module ActionView end end - private def path_to_asset(source, include_host = true) diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb index 014a03c54d..38860431b4 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb @@ -1,10 +1,11 @@ require 'active_support/core_ext/file' +require 'action_view/helpers/asset_paths' module ActionView module Helpers module AssetTagHelper - class AssetPaths + class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc: # You can enable or disable the asset tag ids cache. # With the cache enabled, the asset tag helper methods will make fewer # expensive file system calls (the default implementation checks the file @@ -14,34 +15,6 @@ module ActionView # ActionView::Helpers::AssetTagHelper::AssetPaths.cache_asset_ids = false mattr_accessor :cache_asset_ids - attr_reader :config, :controller - - def initialize(config, controller) - @config = config - @controller = controller - end - - # Add the extension +ext+ if not present. Return full URLs otherwise untouched. - # Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL - # roots. Rewrite the asset path for cache-busting asset ids. Include - # asset host, if configured, with the correct request protocol. - def compute_public_path(source, dir, ext = nil, include_host = true) - return source if is_uri?(source) - - source = rewrite_extension(source, dir, ext) if ext - source = "/#{dir}/#{source}" unless source[0] == ?/ - if controller.respond_to?(:env) && controller.env["action_dispatch.asset_path"] - source = rewrite_asset_path(source, controller.env["action_dispatch.asset_path"]) - end - source = rewrite_asset_path(source, config.asset_path) - - has_request = controller.respond_to?(:request) - source = rewrite_relative_url_root(source, controller.config.relative_url_root) if has_request && include_host - source = rewrite_host_and_protocol(source, has_request) if include_host - - source - end - # Add or change an asset id in the asset id cache. This can be used # for SASS on Heroku. # :api: public @@ -51,101 +24,75 @@ module ActionView end end - def is_uri?(path) - path =~ %r{^[-a-z]+://|^cid:} - end + private - private + def rewrite_extension(source, dir, ext) + source_ext = File.extname(source) - def rewrite_extension(source, dir, ext) - source_ext = File.extname(source) + source_with_ext = if source_ext.empty? + "#{source}.#{ext}" + elsif ext != source_ext[1..-1] + with_ext = "#{source}.#{ext}" + with_ext if File.exist?(File.join(config.assets_dir, dir, with_ext)) + end - source_with_ext = if source_ext.empty? - "#{source}.#{ext}" - elsif ext != source_ext[1..-1] - with_ext = "#{source}.#{ext}" - with_ext if File.exist?(File.join(config.assets_dir, dir, with_ext)) - end + source_with_ext || source + end - source_with_ext || source - end + # Break out the asset path rewrite in case plugins wish to put the asset id + # someplace other than the query string. + def rewrite_asset_path(source, path = nil) + path = config.asset_path - # Break out the asset path rewrite in case plugins wish to put the asset id - # someplace other than the query string. - def rewrite_asset_path(source, path = nil) - if path && path.respond_to?(:call) - return path.call(source) - elsif path && path.is_a?(String) - return path % [source] - end + if path && path.respond_to?(:call) + return path.call(source) + elsif path && path.is_a?(String) + return path % [source] + end - asset_id = rails_asset_id(source) - if asset_id.empty? - source - else - "#{source}?#{asset_id}" - end + asset_id = rails_asset_id(source) + if asset_id.empty? + source + else + "#{source}?#{asset_id}" end + end - mattr_accessor :asset_ids_cache - self.asset_ids_cache = {} + mattr_accessor :asset_ids_cache + self.asset_ids_cache = {} - mattr_accessor :asset_ids_cache_guard - self.asset_ids_cache_guard = Mutex.new + mattr_accessor :asset_ids_cache_guard + self.asset_ids_cache_guard = Mutex.new - # Use the RAILS_ASSET_ID environment variable or the source's - # modification time as its cache-busting asset id. - def rails_asset_id(source) - if asset_id = ENV["RAILS_ASSET_ID"] + # Use the RAILS_ASSET_ID environment variable or the source's + # modification time as its cache-busting asset id. + def rails_asset_id(source) + if asset_id = ENV["RAILS_ASSET_ID"] + asset_id + else + if self.cache_asset_ids && (asset_id = self.asset_ids_cache[source]) asset_id else - if self.cache_asset_ids && (asset_id = self.asset_ids_cache[source]) - asset_id - else - path = File.join(config.assets_dir, source) - asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : '' + path = File.join(config.assets_dir, source) + asset_id = File.exist?(path) ? File.mtime(path).to_i.to_s : '' - if self.cache_asset_ids - add_to_asset_ids_cache(source, asset_id) - end - - asset_id + if self.cache_asset_ids + add_to_asset_ids_cache(source, asset_id) end - end - end - - def rewrite_relative_url_root(source, relative_url_root) - relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source - end - def rewrite_host_and_protocol(source, has_request) - host = compute_asset_host(source) - if has_request && host && !is_uri?(host) - host = "#{controller.request.protocol}#{host}" + asset_id end - "#{host}#{source}" end + end - # Pick an asset host for this source. Returns +nil+ if no host is set, - # the host if no wildcard is set, the host interpolated with the - # numbers 0-3 if it contains <tt>%d</tt> (the number is the source hash mod 4), - # or the value returned from invoking the proc if it's a proc or the value from - # invoking call if it's an object responding to call. - def compute_asset_host(source) - if host = config.asset_host - if host.is_a?(Proc) || host.respond_to?(:call) - case host.is_a?(Proc) ? host.arity : host.method(:call).arity - when 2 - request = controller.respond_to?(:request) && controller.request - host.call(source, request) - else - host.call(source) - end - else - (host =~ /%d/) ? host % (source.hash % 4) : host - end - end - end + def rewrite_relative_url_root(source, relative_url_root) + relative_url_root && !source.starts_with?("#{relative_url_root}/") ? "#{relative_url_root}#{source}" : source + end + + def rewrite_host_and_protocol(source, has_request) + source = rewrite_relative_url_root(source, controller.config.relative_url_root) if has_request + super(source, has_request) + end end end diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb index ce5a7dc2e5..3d815b5e1f 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb @@ -1,6 +1,5 @@ require 'active_support/concern' require 'active_support/core_ext/file' -require 'action_view/helpers/tag_helper' require 'action_view/helpers/asset_tag_helpers/asset_include_tag' module ActionView @@ -8,8 +7,6 @@ module ActionView module AssetTagHelper class JavascriptIncludeTag < AssetIncludeTag - include TagHelper - def asset_name 'javascript' end @@ -80,14 +77,14 @@ module ActionView # Used internally by javascript_include_tag to build the script path. # # ==== Examples - # javascript_path "xmlhr" # => /javascripts/xmlhr.js - # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js - # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js - # javascript_path "http://www.railsapplication.com/js/xmlhr" # => http://www.railsapplication.com/js/xmlhr - # javascript_path "http://www.railsapplication.com/js/xmlhr.js" # => http://www.railsapplication.com/js/xmlhr.js + # javascript_path "xmlhr" # => /javascripts/xmlhr.js + # javascript_path "dir/xmlhr.js" # => /javascripts/dir/xmlhr.js + # javascript_path "/dir/xmlhr" # => /dir/xmlhr.js + # javascript_path "http://www.example.com/js/xmlhr" # => http://www.example.com/js/xmlhr + # javascript_path "http://www.example.com/js/xmlhr.js" # => http://www.example.com/js/xmlhr.js def javascript_path(source) if config.use_sprockets - sprockets_javascript_path(source) + asset_path(source, 'js') else asset_paths.compute_public_path(source, 'javascripts', 'js') end @@ -126,11 +123,11 @@ module ActionView # # => <script type="text/javascript" src="/javascripts/common.javascript?1284139606"></script> # # <script type="text/javascript" src="/elsewhere/cools.js?1423139606"></script> # - # javascript_include_tag "http://www.railsapplication.com/xmlhr" - # # => <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js?1284139606"></script> + # javascript_include_tag "http://www.example.com/xmlhr" + # # => <script type="text/javascript" src="http://www.example.com/xmlhr.js?1284139606"></script> # - # javascript_include_tag "http://www.railsapplication.com/xmlhr.js" - # # => <script type="text/javascript" src="http://www.railsapplication.com/xmlhr.js?1284139606"></script> + # javascript_include_tag "http://www.example.com/xmlhr.js" + # # => <script type="text/javascript" src="http://www.example.com/xmlhr.js?1284139606"></script> # # javascript_include_tag :defaults # # => <script type="text/javascript" src="/javascripts/jquery.js?1284139606"></script> diff --git a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb index a994afb65e..a95eb221be 100644 --- a/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb +++ b/actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb @@ -1,6 +1,5 @@ require 'active_support/concern' require 'active_support/core_ext/file' -require 'action_view/helpers/tag_helper' require 'action_view/helpers/asset_tag_helpers/asset_include_tag' module ActionView @@ -8,8 +7,6 @@ module ActionView module AssetTagHelper class StylesheetIncludeTag < AssetIncludeTag - include TagHelper - def asset_name 'stylesheet' end @@ -57,14 +54,14 @@ module ActionView # Used internally by +stylesheet_link_tag+ to build the stylesheet path. # # ==== Examples - # stylesheet_path "style" # => /stylesheets/style.css - # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css - # stylesheet_path "/dir/style.css" # => /dir/style.css - # stylesheet_path "http://www.railsapplication.com/css/style" # => http://www.railsapplication.com/css/style - # stylesheet_path "http://www.railsapplication.com/css/style.css" # => http://www.railsapplication.com/css/style.css + # stylesheet_path "style" # => /stylesheets/style.css + # stylesheet_path "dir/style.css" # => /stylesheets/dir/style.css + # stylesheet_path "/dir/style.css" # => /dir/style.css + # stylesheet_path "http://www.example.com/css/style" # => http://www.example.com/css/style + # stylesheet_path "http://www.example.com/css/style.css" # => http://www.example.com/css/style.css def stylesheet_path(source) if config.use_sprockets - sprockets_stylesheet_path(source) + asset_path(source, 'css') else asset_paths.compute_public_path(source, 'stylesheets', 'css') end @@ -82,8 +79,8 @@ module ActionView # stylesheet_link_tag "style.css" # => # <link href="/stylesheets/style.css" media="screen" rel="stylesheet" type="text/css" /> # - # stylesheet_link_tag "http://www.railsapplication.com/style.css" # => - # <link href="http://www.railsapplication.com/style.css" media="screen" rel="stylesheet" type="text/css" /> + # stylesheet_link_tag "http://www.example.com/style.css" # => + # <link href="http://www.example.com/style.css" media="screen" rel="stylesheet" type="text/css" /> # # stylesheet_link_tag "style", :media => "all" # => # <link href="/stylesheets/style.css" media="all" rel="stylesheet" type="text/css" /> diff --git a/actionpack/lib/action_view/helpers/capture_helper.rb b/actionpack/lib/action_view/helpers/capture_helper.rb index 3808e231f1..0139714240 100644 --- a/actionpack/lib/action_view/helpers/capture_helper.rb +++ b/actionpack/lib/action_view/helpers/capture_helper.rb @@ -135,8 +135,19 @@ module ActionView # for elements that will be fragment cached. def content_for(name, content = nil, &block) content = capture(&block) if block_given? - @_content_for[name] << content if content - @_content_for[name] unless content + result = @_view_flow.append(name, content) if content + result unless content + end + + # The same as +content_for+ but when used with streaming flushes + # straight back to the layout. In other words, if you want to + # concatenate several times to the same buffer when rendering a given + # template, you should use +content_for+, if not, use +provide+ to tell + # the layout to stop looking for more contents. + def provide(name, content = nil, &block) + content = capture(&block) if block_given? + result = @_view_flow.append!(name, content) if content + result unless content end # content_for? simply checks whether any content has been captured yet using content_for @@ -158,7 +169,7 @@ module ActionView # </body> # </html> def content_for?(name) - @_content_for[name].present? + @_view_flow.get(name).present? end # Use an alternate output buffer for the duration of the block. diff --git a/actionpack/lib/action_view/helpers/date_helper.rb b/actionpack/lib/action_view/helpers/date_helper.rb index 313a2591bf..9277359d5c 100644 --- a/actionpack/lib/action_view/helpers/date_helper.rb +++ b/actionpack/lib/action_view/helpers/date_helper.rb @@ -51,7 +51,7 @@ module ActionView # distance_of_time_in_words(from_time, from_time + 15.seconds) # => less than a minute # distance_of_time_in_words(from_time, from_time + 15.seconds, true) # => less than 20 seconds # distance_of_time_in_words(from_time, 3.years.from_now) # => about 3 years - # distance_of_time_in_words(from_time, from_time + 60.hours) # => about 3 days + # distance_of_time_in_words(from_time, from_time + 60.hours) # => 3 days # distance_of_time_in_words(from_time, from_time + 45.seconds, true) # => less than a minute # distance_of_time_in_words(from_time, from_time - 45.seconds, true) # => less than a minute # distance_of_time_in_words(from_time, 76.seconds.from_now) # => 1 minute @@ -112,10 +112,12 @@ module ActionView # # ==== Examples # time_ago_in_words(3.minutes.from_now) # => 3 minutes - # time_ago_in_words(Time.now - 15.hours) # => 15 hours + # time_ago_in_words(Time.now - 15.hours) # => about 15 hours # time_ago_in_words(Time.now) # => less than a minute # - # from_time = Time.now - 3.days - 14.minutes - 25.seconds # => 3 days + # from_time = Time.now - 3.days - 14.minutes - 25.seconds + # time_ago_in_words(from_time) # => 3 days + # def time_ago_in_words(from_time, include_seconds = false) distance_of_time_in_words(from_time, Time.now, include_seconds) end @@ -464,7 +466,7 @@ module ActionView # # # Generates a select field for hours with a custom prompt. Use :prompt => true for a # # generic prompt. - # select_hour(13, :prompt =>'Choose hour') + # select_hour(13, :prompt => 'Choose hour') # def select_hour(datetime, options = {}, html_options = {}) DateTimeSelector.new(datetime, options, html_options).select_hour diff --git a/actionpack/lib/action_view/helpers/form_helper.rb b/actionpack/lib/action_view/helpers/form_helper.rb index 9025d9e24c..440acafa88 100644 --- a/actionpack/lib/action_view/helpers/form_helper.rb +++ b/actionpack/lib/action_view/helpers/form_helper.rb @@ -610,10 +610,11 @@ module ActionView # label(:post, :body) # # => <label for="post_body">Write your entire text here</label> # - # Localization can also be based purely on the translation of the attribute-name like this: + # Localization can also be based purely on the translation of the attribute-name + # (if you are using ActiveRecord): # - # activemodel: - # attribute: + # activerecord: + # attributes: # post: # cost: "Total cost" # diff --git a/actionpack/lib/action_view/helpers/number_helper.rb b/actionpack/lib/action_view/helpers/number_helper.rb index 05a9c5b4f1..b545031fcc 100644 --- a/actionpack/lib/action_view/helpers/number_helper.rb +++ b/actionpack/lib/action_view/helpers/number_helper.rb @@ -100,7 +100,7 @@ module ActionView # number_to_currency(1234567890.506, :precision => 3) # => $1,234,567,890.506 # number_to_currency(1234567890.506, :locale => :fr) # => 1 234 567 890,506 € # - # number_to_currency(1234567890.50, :negative_format => "(%u%n)") + # number_to_currency(-1234567890.50, :negative_format => "(%u%n)") # # => ($1,234,567,890.51) # number_to_currency(1234567890.50, :unit => "£", :separator => ",", :delimiter => "") # # => £1234567890,50 @@ -238,7 +238,7 @@ module ActionView # number_with_precision(111.2345, :precision => 1, :significant => true) # => 100 # number_with_precision(13, :precision => 5, :significant => true) # => 13.000 # number_with_precision(111.234, :locale => :fr) # => 111,234 - # number_with_precision(13, :precision => 5, :significant => true, strip_insignificant_zeros => true) + # number_with_precision(13, :precision => 5, :significant => true, :strip_insignificant_zeros => true) # # => 13 # number_with_precision(389.32314, :precision => 4, :significant => true) # => 389.3 # number_with_precision(1111.2345, :precision => 2, :separator => ',', :delimiter => '.') @@ -318,7 +318,7 @@ module ActionView # Non-significant zeros after the fractional separator are stripped out by default (set # <tt>:strip_insignificant_zeros</tt> to +false+ to change that): # number_to_human_size(1234567890123, :precision => 5) # => "1.1229 TB" - # number_to_human_size(524288000, :precision=>5) # => "500 MB" + # number_to_human_size(524288000, :precision => 5) # => "500 MB" def number_to_human_size(number, options = {}) options.symbolize_keys! @@ -407,7 +407,7 @@ module ActionView # Unsignificant zeros after the decimal separator are stripped out by default (set # <tt>:strip_insignificant_zeros</tt> to +false+ to change that): # number_to_human(12345012345, :significant_digits => 6) # => "12.345 Billion" - # number_to_human(500000000, :precision=>5) # => "500 Million" + # number_to_human(500000000, :precision => 5) # => "500 Million" # # ==== Custom Unit Quantifiers # diff --git a/actionpack/lib/action_view/helpers/record_tag_helper.rb b/actionpack/lib/action_view/helpers/record_tag_helper.rb index 4d300a1469..142a25f118 100644 --- a/actionpack/lib/action_view/helpers/record_tag_helper.rb +++ b/actionpack/lib/action_view/helpers/record_tag_helper.rb @@ -10,7 +10,7 @@ module ActionView # relate to the specified Active Record object. Usage example: # # <%= div_for(@person, :class => "foo") do %> - # <%=h @person.name %> + # <%= @person.name %> # <% end %> # # produces: @@ -25,8 +25,8 @@ module ActionView # that relate to the specified Active Record object. For example: # # <%= content_tag_for(:tr, @person) do %> - # <td><%=h @person.first_name %></td> - # <td><%=h @person.last_name %></td> + # <td><%= @person.first_name %></td> + # <td><%= @person.last_name %></td> # <% end %> # # would produce the following HTML (assuming @person is an instance of diff --git a/actionpack/lib/action_view/helpers/sanitize_helper.rb b/actionpack/lib/action_view/helpers/sanitize_helper.rb index 0fee34f8a4..841be0a567 100644 --- a/actionpack/lib/action_view/helpers/sanitize_helper.rb +++ b/actionpack/lib/action_view/helpers/sanitize_helper.rb @@ -94,7 +94,7 @@ module ActionView # # => Please e-mail me at me@email.com. # # strip_links('Blog: <a href="http://www.myblog.com/" class="nav" target=\"_blank\">Visit</a>.') - # # => Blog: Visit + # # => Blog: Visit. def strip_links(html) self.class.link_sanitizer.sanitize(html) end diff --git a/actionpack/lib/action_view/helpers/sprockets_helper.rb b/actionpack/lib/action_view/helpers/sprockets_helper.rb index 408a2030ab..b43b91178c 100644 --- a/actionpack/lib/action_view/helpers/sprockets_helper.rb +++ b/actionpack/lib/action_view/helpers/sprockets_helper.rb @@ -1,85 +1,69 @@ require 'uri' +require 'action_view/helpers/asset_paths' module ActionView module Helpers module SprocketsHelper - def sprockets_javascript_path(source) - compute_sprockets_path source, 'assets', 'js' + def asset_path(source, default_ext = nil) + sprockets_asset_paths.compute_public_path(source, 'assets', default_ext, true) end def sprockets_javascript_include_tag(source, options = {}) options = { 'type' => "text/javascript", - 'src' => sprockets_javascript_path(source) + 'src' => asset_path(source, 'js') }.merge(options.stringify_keys) content_tag 'script', "", options end - def sprockets_stylesheet_path(source) - compute_sprockets_path source, 'assets', 'css' - end - def sprockets_stylesheet_link_tag(source, options = {}) options = { 'rel' => "stylesheet", 'type' => "text/css", 'media' => "screen", - 'href' => sprockets_stylesheet_path(source) + 'href' => asset_path(source, 'css') }.merge(options.stringify_keys) tag 'link', options end private - def compute_sprockets_path(source, dir, default_ext) - source = source.to_s - - return source if URI.parse(source).host - - # Add /javscripts to relative paths - if source[0] != ?/ - source = "/#{dir}/#{source}" - end - # Add default extension if there isn't one - if default_ext && File.extname(source).empty? - source = "#{source}.#{default_ext}" - end + def sprockets_asset_paths + @sprockets_asset_paths ||= begin + config = self.config if respond_to?(:config) + controller = self.controller if respond_to?(:controller) + SprocketsHelper::AssetPaths.new(config, controller) + end + end - # Fingerprint url + class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc: + def rewrite_asset_path(source, dir) if source =~ /^\/#{dir}\/(.+)/ - source = assets.path($1, config.perform_caching, dir) + assets.path($1, performing_caching?, dir) + else + source end - - host = compute_asset_host(source) - - if controller.respond_to?(:request) && host && URI.parse(host).host - source = "#{controller.request.protocol}#{host}#{source}" - end - - source end - def compute_asset_host(source) - if host = config.asset_host - if host.is_a?(Proc) || host.respond_to?(:call) - case host.is_a?(Proc) ? host.arity : host.method(:call).arity - when 2 - request = controller.respond_to?(:request) && controller.request - host.call(source, request) - else - host.call(source) - end - else - (host =~ /%d/) ? host % (source.hash % 4) : host - end + def rewrite_extension(source, dir, ext) + if ext && File.extname(source).empty? + "#{source}.#{ext}" + else + source end end def assets Rails.application.assets end + + # When included in Sprockets::Context, we need to ask the top-level config as the controller is not available + def performing_caching? + @config ? @config.perform_caching : Rails.application.config.action_controller.perform_caching + end + end end end -end +end
\ No newline at end of file diff --git a/actionpack/lib/action_view/helpers/text_helper.rb b/actionpack/lib/action_view/helpers/text_helper.rb index bdda1df437..06e2b027da 100644 --- a/actionpack/lib/action_view/helpers/text_helper.rb +++ b/actionpack/lib/action_view/helpers/text_helper.rb @@ -19,7 +19,7 @@ module ActionView # simple_format('<a href="http://example.com/">Example</a>') # # => "<p><a href=\"http://example.com/\">Example</a></p>" # - # simple_format('<a href="javascript:alert('no!')">Example</a>') + # simple_format('<a href="javascript:alert(\'no!\')">Example</a>') # # => "<p><a>Example</a></p>" # # If you want to escape all content, you should invoke the +h+ method before @@ -295,11 +295,11 @@ module ActionView # +link+ as its optional second parameter and the +html_options+ hash # as its optional third parameter: # post_body = "Welcome to my new blog at http://www.myblog.com/. Please e-mail me at me@email.com." - # auto_link(post_body, :urls) # => Once upon\na time + # auto_link(post_body, :urls) # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\">http://www.myblog.com</a>. # Please e-mail me at me@email.com." # - # auto_link(post_body, :all, :target => "_blank") # => Once upon\na time + # auto_link(post_body, :all, :target => "_blank") # # => "Welcome to my new blog at <a href=\"http://www.myblog.com/\" target=\"_blank\">http://www.myblog.com</a>. # Please e-mail me at <a href=\"mailto:me@email.com\">me@email.com</a>." def auto_link(text, *args, &block)#link = :all, html = {}, &block) diff --git a/actionpack/lib/action_view/helpers/url_helper.rb b/actionpack/lib/action_view/helpers/url_helper.rb index de75488e72..ffa9a5bb0b 100644 --- a/actionpack/lib/action_view/helpers/url_helper.rb +++ b/actionpack/lib/action_view/helpers/url_helper.rb @@ -68,7 +68,7 @@ module ActionView # # => /books/find # # <%= url_for(:action => 'login', :controller => 'members', :only_path => false, :protocol => 'https') %> - # # => https://www.railsapplication.com/members/login/ + # # => https://www.example.com/members/login/ # # <%= url_for(:action => 'play', :anchor => 'player') %> # # => /messages/play/#player @@ -555,10 +555,10 @@ module ActionView # current_page?(:controller => 'shop', :action => 'checkout') # # => true # - # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc', :page=>'1') + # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc', :page => '1') # # => true # - # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc', :page=>'2') + # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc', :page => '2') # # => false # # current_page?(:controller => 'shop', :action => 'checkout', :order => 'desc') diff --git a/actionpack/lib/action_view/renderer/partial_renderer.rb b/actionpack/lib/action_view/renderer/partial_renderer.rb index 94c0a8a8fb..10cd37d56f 100644 --- a/actionpack/lib/action_view/renderer/partial_renderer.rb +++ b/actionpack/lib/action_view/renderer/partial_renderer.rb @@ -1,5 +1,3 @@ -require 'action_view/renderer/abstract_renderer' - module ActionView class PartialRenderer < AbstractRenderer #:nodoc: PARTIAL_NAMES = Hash.new {|h,k| h[k] = {} } @@ -79,7 +77,7 @@ module ActionView locals[as] = object content = @template.render(view, locals) do |*name| - view._layout_for(*name, &block) + view._block_layout_for(*name, &block) end content = layout.render(view, locals){ content } if layout diff --git a/actionpack/lib/action_view/renderer/streaming_template_renderer.rb b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb new file mode 100644 index 0000000000..03aab444f8 --- /dev/null +++ b/actionpack/lib/action_view/renderer/streaming_template_renderer.rb @@ -0,0 +1,130 @@ +# 1.9 ships with Fibers but we need to require the extra +# methods explicitly. We only load those extra methods if +# Fiber is available in the first place. +require 'fiber' if defined?(Fiber) + +module ActionView + # Consider the following layout: + # + # <%= yield :header %> + # 2 + # <%= yield %> + # 5 + # <%= yield :footer %> + # + # And template: + # + # <%= provide :header, "1" %> + # 3 + # 4 + # <%= provide :footer, "6" %> + # + # It will stream: + # + # "1\n", "2\n", "3\n4\n", "5\n", "6\n" + # + # Notice that once you <%= yield %>, it will render the whole template + # before streaming again. In the future, we can also support streaming + # from the template and not only the layout. + # + # Also, notice we use +provide+ instead of +content_for+, as +provide+ + # gives the control back to the layout as soon as it is called. + # With +content_for+, it would render all the template to find all + # +content_for+ calls. For instance, consider this layout: + # + # <%= yield :header %> + # + # With this template: + # + # <%= content_for :header, "1" %> + # <%= provide :header, "2" %> + # <%= provide :header, "3" %> + # + # It will return "12\n" because +content_for+ continues rendering the + # template but it is returns back to the layout as soon as it sees the + # first +provide+. + # + # == TODO + # + # * Support streaming from child templates, partials and so on. + # * Integrate exceptions with exceptron + class StreamingTemplateRenderer < TemplateRenderer #:nodoc: + # A valid Rack::Body (i.e. it responds to each). + # It is initialized with a block that, when called, starts + # rendering the template. + class Body #:nodoc: + def initialize(&start) + @start = start + end + + def each(&block) + begin + @start.call(block) + rescue + block.call ActionView::Base.streaming_completion_on_exception + end + self + end + end + + # For streaming, instead of rendering a given a template, we return a Body + # object that responds to each. This object is initialized with a block + # that knows how to render the template. + def render_template(template, layout_name = nil, locals = {}) #:nodoc: + return [super] unless layout_name && template.supports_streaming? + + locals ||= {} + layout = layout_name && find_layout(layout_name, locals.keys) + + Body.new do |buffer| + delayed_render(buffer, template, layout, @view, locals) + end + end + + private + + def delayed_render(buffer, template, layout, view, locals) + # Wrap the given buffer in the StreamingBuffer and pass it to the + # underlying template handler. Now, everytime something is concatenated + # to the buffer, it is not appended to an array, but streamed straight + # to the client. + output = ActionView::StreamingBuffer.new(buffer) + yielder = lambda { |*name| view._layout_for(*name) } + + instrument(:template, :identifier => template.identifier, :layout => layout.try(:virtual_path)) do + fiber = Fiber.new do + if layout + layout.render(view, locals, output, &yielder) + else + # If you don't have a layout, just render the thing + # and concatenate the final result. This is the same + # as a layout with just <%= yield %> + output.safe_concat view._layout_for + end + end + + # Set the view flow to support streaming. It will be aware + # when to stop rendering the layout because it needs to search + # something in the template and vice-versa. + view._view_flow = StreamingFlow.new(view, fiber) + + # Yo! Start the fiber! + fiber.resume + + # If the fiber is still alive, it means we need something + # from the template, so start rendering it. If not, it means + # the layout exited without requiring anything from the template. + if fiber.alive? + content = template.render(view, locals, &yielder) + + # Once rendering the template is done, sets its content in the :layout key. + view._view_flow.set(:layout, content) + + # In case the layout continues yielding, we need to resume + # the fiber until all yields are handled. + fiber.resume while fiber.alive? + end + end + end + end +end diff --git a/actionpack/lib/action_view/renderer/template_renderer.rb b/actionpack/lib/action_view/renderer/template_renderer.rb index 9ae1636131..6b5ead463f 100644 --- a/actionpack/lib/action_view/renderer/template_renderer.rb +++ b/actionpack/lib/action_view/renderer/template_renderer.rb @@ -1,41 +1,16 @@ -require 'set' require 'active_support/core_ext/object/try' require 'active_support/core_ext/array/wrap' -require 'action_view/renderer/abstract_renderer' module ActionView class TemplateRenderer < AbstractRenderer #:nodoc: - attr_reader :rendered - - def initialize(view) - super - @rendered = Set.new - end - def render(options) wrap_formats(options[:template] || options[:file]) do template = determine_template(options) + freeze_formats(template.formats, true) render_template(template, options[:layout], options[:locals]) end end - def render_once(options) - paths, locals = options[:once], options[:locals] || {} - layout, keys = options[:layout], locals.keys - prefixes = options.fetch(:prefixes, @view.controller_prefixes) - - raise "render :once expects a String or an Array to be given" unless paths - - render_with_layout(layout, locals) do - contents = [] - Array.wrap(paths).each do |path| - template = find_template(path, prefixes, false, keys) - contents << render_template(template, nil, locals) if @rendered.add?(template) - end - contents.join("\n") - end - end - # Determine the template to be rendered using the given options. def determine_template(options) #:nodoc: keys = options[:locals].try(:keys) || [] @@ -56,7 +31,6 @@ module ActionView # Renders the given template. An string representing the layout can be # supplied as well. def render_template(template, layout_name = nil, locals = {}) #:nodoc: - freeze_formats(template.formats, true) view, locals = @view, locals || {} render_with_layout(layout_name, locals) do |layout| @@ -72,7 +46,7 @@ module ActionView if layout view = @view - view.store_content_for(:layout, content) + view._view_flow.set(:layout, content) layout.render(view, locals){ |*name| view._layout_for(*name) } else content diff --git a/actionpack/lib/action_view/rendering.rb b/actionpack/lib/action_view/rendering.rb index 2b488fa845..2bce2fb045 100644 --- a/actionpack/lib/action_view/rendering.rb +++ b/actionpack/lib/action_view/rendering.rb @@ -9,7 +9,6 @@ module ActionView # * <tt>:file</tt> - Renders an explicit template file (this used to be the old default), add :locals to pass in those. # * <tt>:inline</tt> - Renders an inline template similar to how it's done in the controller. # * <tt>:text</tt> - Renders the text passed in out. - # * <tt>:once</tt> - Accepts a string or an array of strings and Rails will ensure they each of them are rendered just once. # # If no options hash is passed or :update specified, the default is to render a partial and use the second parameter # as the locals hash. @@ -20,8 +19,6 @@ module ActionView _render_partial(options.merge(:partial => options[:layout]), &block) elsif options.key?(:partial) _render_partial(options) - elsif options.key?(:once) - _render_once(options) else _render_template(options) end @@ -30,6 +27,19 @@ module ActionView end end + # Render but returns a valid Rack body. If fibers are defined, we return + # a streaming body that renders the template piece by piece. + # + # Note that partials are not supported to be rendered with streaming, + # so in such cases, we just wrap them in an array. + def render_body(options) + if options.key?(:partial) + [_render_partial(options)] + else + StreamingTemplateRenderer.new(self).render(options) + end + end + # Returns the contents that are yielded to a layout, given a name or a block. # # You can think of a layout as a method that is called with a block. If the user calls @@ -76,22 +86,23 @@ module ActionView # Hello David # </html> # - def _layout_for(*args, &block) + def _layout_for(*args) + name = args.first + name = :layout unless name.is_a?(Symbol) + @_view_flow.get(name).html_safe + end + + # Handle layout for calls from partials that supports blocks. + def _block_layout_for(*args, &block) name = args.first - if name.is_a?(Symbol) - @_content_for[name].html_safe - elsif block + if !name.is_a?(Symbol) && block capture(*args, &block) else - @_content_for[:layout].html_safe + _layout_for(*args) end end - def _render_once(options) #:nodoc: - _template_renderer.render_once(options) - end - def _render_template(options) #:nodoc: _template_renderer.render(options) end diff --git a/actionpack/lib/action_view/template.rb b/actionpack/lib/action_view/template.rb index 96d506fac5..6dfc4f68ae 100644 --- a/actionpack/lib/action_view/template.rb +++ b/actionpack/lib/action_view/template.rb @@ -126,17 +126,23 @@ module ActionView @formats = Array.wrap(format).map { |f| f.is_a?(Mime::Type) ? f.ref : f } end + # Returns if the underlying handler supports streaming. If so, + # a streaming buffer *may* be passed when it start rendering. + def supports_streaming? + handler.respond_to?(:supports_streaming?) && handler.supports_streaming? + end + # Render a template. If the template was not compiled yet, it is done # exactly before rendering. # # This method is instrumented as "!render_template.action_view". Notice that # we use a bang in this instrumentation because you don't want to # consume this in production. This is only slow if it's being listened to. - def render(view, locals, &block) + def render(view, locals, buffer=nil, &block) old_template, view._template = view._template, self ActiveSupport::Notifications.instrument("!render_template.action_view", :virtual_path => @virtual_path) do compile!(view) - view.send(method_name, locals, &block) + view.send(method_name, locals, buffer, &block) end rescue Exception => e handle_render_error(view, e) @@ -167,28 +173,6 @@ module ActionView end end - # Expires this template by setting his updated_at date to Jan 1st, 1970. - def expire! - @updated_at = Time.utc(1970) - end - - # Receives a view context and renders a template exactly like self by using - # the @virtual_path. It raises an error if no @virtual_path was given. - def rerender(view) - raise "A template needs to have a virtual path in order to be rerendered" unless @virtual_path - name = @virtual_path.dup - if name.sub!(/(^|\/)_([^\/]*)$/, '\1\2') - view.render :partial => name - else - view.render :template => @virtual_path - end - end - - # Used to store template data by template handlers. - def data - @data ||= {} - end - def inspect @inspect ||= if defined?(Rails.root) @@ -274,13 +258,12 @@ module ActionView end end - arity = @handler.respond_to?(:arity) ? @handler.arity : @handler.method(:call).arity - code = arity.abs == 1 ? @handler.call(self) : @handler.call(self, view) + code = @handler.call(self) # Make sure that the resulting String to be evalled is in the # encoding of the code source = <<-end_src - def #{method_name}(local_assigns) + def #{method_name}(local_assigns, output_buffer) _old_output_buffer = @output_buffer;#{locals_code};#{code} ensure @output_buffer = _old_output_buffer diff --git a/actionpack/lib/action_view/template/handlers/erb.rb b/actionpack/lib/action_view/template/handlers/erb.rb index 0443b1b0f5..7e9e4e518a 100644 --- a/actionpack/lib/action_view/template/handlers/erb.rb +++ b/actionpack/lib/action_view/template/handlers/erb.rb @@ -1,28 +1,14 @@ require 'active_support/core_ext/class/attribute_accessors' -require 'active_support/core_ext/string/output_safety' require 'action_view/template' require 'action_view/template/handler' require 'erubis' module ActionView - class OutputBuffer < ActiveSupport::SafeBuffer - def initialize(*) - super - encode! if encoding_aware? - end - - def <<(value) - super(value.to_s) - end - alias :append= :<< - alias :safe_append= :safe_concat - end - class Template module Handlers class Erubis < ::Erubis::Eruby def add_preamble(src) - src << "@output_buffer = ActionView::OutputBuffer.new;" + src << "@output_buffer = output_buffer || ActionView::OutputBuffer.new;" end def add_text(src, text) @@ -73,6 +59,10 @@ module ActionView new.call(template) end + def supports_streaming? + true + end + def handles_encoding? true end diff --git a/actionpack/lib/sprockets/railtie.rb b/actionpack/lib/sprockets/railtie.rb new file mode 100644 index 0000000000..9c10decd60 --- /dev/null +++ b/actionpack/lib/sprockets/railtie.rb @@ -0,0 +1,100 @@ +module Sprockets + class Railtie < Rails::Railtie + def self.using_coffee? + require 'coffee-script' + defined?(CoffeeScript) + rescue LoadError + false + end + + def self.using_scss? + require 'sass' + defined?(Sass) + rescue LoadError + false + end + + config.app_generators.javascript_engine :coffee if using_coffee? + config.app_generators.stylesheet_engine :scss if using_scss? + + # Configure ActionController to use sprockets. + initializer "sprockets.set_configs", :after => "action_controller.set_configs" do |app| + ActiveSupport.on_load(:action_controller) do + self.use_sprockets = app.config.assets.enabled + end + end + + # We need to configure this after initialization to ensure we collect + # paths from all engines. This hook is invoked exactly before routes + # are compiled. + config.after_initialize do |app| + assets = app.config.assets + next unless assets.enabled + + app.assets = asset_environment(app) + + ActiveSupport.on_load(:action_view) do + app.assets.context.instance_eval do + include ::ActionView::Helpers::SprocketsHelper + end + end + + app.routes.append do + mount app.assets => assets.prefix + end + + if config.action_controller.perform_caching + app.assets = app.assets.index + end + end + + protected + + def asset_environment(app) + require "sprockets" + assets = app.config.assets + env = Sprockets::Environment.new(app.root.to_s) + env.static_root = File.join(app.root.join("public"), assets.prefix) + env.paths.concat assets.paths + env.logger = Rails.logger + env.js_compressor = expand_js_compressor(assets.js_compressor) + env.css_compressor = expand_css_compressor(assets.css_compressor) + env + end + + def expand_js_compressor(sym) + case sym + when :closure + require 'closure-compiler' + Closure::Compiler.new + when :uglifier + require 'uglifier' + Uglifier.new + when :yui + require 'yui/compressor' + YUI::JavaScriptCompressor.new + else + sym + end + end + + def expand_css_compressor(sym) + case sym + when :scss + require 'sass' + compressor = Object.new + def compressor.compress(source) + Sass::Engine.new(source, + :syntax => :scss, :style => :compressed + ).render + end + compressor + when :yui + require 'yui/compressor' + YUI::JavaScriptCompressor.new(:munge => true) + else + sym + end + end + end +end diff --git a/actionpack/test/controller/flash_test.rb b/actionpack/test/controller/flash_test.rb index 9c89f1334d..e19612eace 100644 --- a/actionpack/test/controller/flash_test.rb +++ b/actionpack/test/controller/flash_test.rb @@ -264,6 +264,22 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest end end + def test_setting_flash_does_not_raise_in_following_requests + with_test_route_set do + env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new } + get '/set_flash', nil, env + get '/set_flash', nil, env + end + end + + def test_setting_flash_now_does_not_raise_in_following_requests + with_test_route_set do + env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new } + get '/set_flash_now', nil, env + get '/set_flash_now', nil, env + end + end + def test_setting_flash_raises_after_stream_back_to_client_even_with_an_empty_flash with_test_route_set do env = { 'action_dispatch.request.flash_hash' => ActionDispatch::Flash::FlashHash.new } @@ -294,7 +310,6 @@ class FlashIntegrationTest < ActionDispatch::IntegrationTest end end - private # Overwrite get to send SessionSecret in env hash diff --git a/actionpack/test/controller/new_base/render_once_test.rb b/actionpack/test/controller/new_base/render_once_test.rb deleted file mode 100644 index 175abf8a7e..0000000000 --- a/actionpack/test/controller/new_base/render_once_test.rb +++ /dev/null @@ -1,86 +0,0 @@ -require 'abstract_unit' - -module RenderTemplate - class RenderOnceController < ActionController::Base - layout false - - RESOLVER = ActionView::FixtureResolver.new( - "test/a.html.erb" => "a", - "test/b.html.erb" => "<>", - "test/c.html.erb" => "c", - "test/one.html.erb" => "<%= render :once => 'result' %>", - "test/two.html.erb" => "<%= render :once => 'result' %>", - "test/three.html.erb" => "<%= render :once => 'result' %>", - "test/result.html.erb" => "YES!", - "other/result.html.erb" => "NO!", - "layouts/test.html.erb" => "l<%= yield %>l" - ) - - self.view_paths = [RESOLVER] - - def _prefixes - %w(test) - end - - def multiple - render :once => %w(a b c) - end - - def once - render :once => %w(one two three) - end - - def duplicate - render :once => %w(a a a) - end - - def with_layout - render :once => %w(a b c), :layout => "test" - end - - def with_prefix - render :once => "result", :prefixes => %w(other) - end - - def with_nil_prefix - render :once => "test/result", :prefixes => [] - end - end - - module Tests - def test_mutliple_arguments_get_all_rendered - get :multiple - assert_response "a\n<>\nc" - end - - def test_referenced_templates_get_rendered_once - get :once - assert_response "YES!\n\n" - end - - def test_duplicated_templates_get_rendered_once - get :duplicate - assert_response "a" - end - - def test_layout_wraps_all_rendered_templates - get :with_layout - assert_response "la\n<>\ncl" - end - - def test_with_prefix_option - get :with_prefix - assert_response "NO!" - end - - def test_with_nil_prefix_option - get :with_nil_prefix - assert_response "YES!" - end - end - - class TestRenderOnce < Rack::TestCase - testing RenderTemplate::RenderOnceController - include Tests - end -end diff --git a/actionpack/test/controller/new_base/render_streaming_test.rb b/actionpack/test/controller/new_base/render_streaming_test.rb new file mode 100644 index 0000000000..fed8d40b47 --- /dev/null +++ b/actionpack/test/controller/new_base/render_streaming_test.rb @@ -0,0 +1,101 @@ +require 'abstract_unit' + +module RenderStreaming + class BasicController < ActionController::Base + self.view_paths = [ActionView::FixtureResolver.new( + "render_streaming/basic/hello_world.html.erb" => "Hello world", + "render_streaming/basic/boom.html.erb" => "<%= nil.invalid! %>", + "layouts/application.html.erb" => "<%= yield %>, I'm here!", + "layouts/boom.html.erb" => "<body class=\"<%= nil.invalid! %>\"<%= yield %></body>" + )] + + layout "application" + stream :only => [:hello_world, :skip] + + def hello_world + end + + def layout_exception + render :action => "hello_world", :stream => true, :layout => "boom" + end + + def template_exception + render :action => "boom", :stream => true + end + + def skip + render :action => "hello_world", :stream => false + end + + def explicit + render :action => "hello_world", :stream => true + end + + def no_layout + render :action => "hello_world", :stream => true, :layout => false + end + + def explicit_cache + headers["Cache-Control"] = "private" + render :action => "hello_world", :stream => true + end + end + + class StreamingTest < Rack::TestCase + test "rendering with streaming enabled at the class level" do + get "/render_streaming/basic/hello_world" + assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n" + assert_streaming! + end + + test "rendering with streaming given to render" do + get "/render_streaming/basic/explicit" + assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n" + assert_streaming! + end + + test "rendering with streaming do not override explicit cache control given to render" do + get "/render_streaming/basic/explicit_cache" + assert_body "b\r\nHello world\r\nb\r\n, I'm here!\r\n0\r\n\r\n" + assert_streaming! "private" + end + + test "rendering with streaming no layout" do + get "/render_streaming/basic/no_layout" + assert_body "b\r\nHello world\r\n0\r\n\r\n" + assert_streaming! + end + + test "skip rendering with streaming at render level" do + get "/render_streaming/basic/skip" + assert_body "Hello world, I'm here!" + end + + test "rendering with layout exception" do + get "/render_streaming/basic/layout_exception" + assert_body "d\r\n<body class=\"\r\n4e\r\n\"><script type=\"text/javascript\">window.location = \"/500.html\"</script></html>\r\n0\r\n\r\n" + assert_streaming! + end + + test "rendering with template exception" do + get "/render_streaming/basic/template_exception" + assert_body "4e\r\n\"><script type=\"text/javascript\">window.location = \"/500.html\"</script></html>\r\n0\r\n\r\n" + assert_streaming! + end + + test "do not stream on HTTP/1.0" do + get "/render_streaming/basic/hello_world", nil, "HTTP_VERSION" => "HTTP/1.0" + assert_body "Hello world, I'm here!" + assert_status 200 + assert_equal "22", headers["Content-Length"] + assert_equal nil, headers["Transfer-Encoding"] + end + + def assert_streaming!(cache="no-cache") + assert_status 200 + assert_equal nil, headers["Content-Length"] + assert_equal "chunked", headers["Transfer-Encoding"] + assert_equal cache, headers["Cache-Control"] + end + end +end if defined?(Fiber) diff --git a/actionpack/test/controller/new_base/render_test.rb b/actionpack/test/controller/new_base/render_test.rb index d6062bfa8c..60468bf5c7 100644 --- a/actionpack/test/controller/new_base/render_test.rb +++ b/actionpack/test/controller/new_base/render_test.rb @@ -81,8 +81,7 @@ module Render end class TestOnlyRenderPublicActions < Rack::TestCase - describe "Only public methods on actual controllers are callable actions" - + # Only public methods on actual controllers are callable actions test "raises an exception when a method of Object is called" do assert_raises(AbstractController::ActionNotFound) do get "/render/blank_render/clone", {}, "action_dispatch.show_exceptions" => false diff --git a/actionpack/test/controller/view_paths_test.rb b/actionpack/test/controller/view_paths_test.rb index 9280a1c2d3..42356be1ea 100644 --- a/actionpack/test/controller/view_paths_test.rb +++ b/actionpack/test/controller/view_paths_test.rb @@ -131,8 +131,8 @@ class ViewLoadPathsTest < ActionController::TestCase assert_equal "Hello overridden world!", @response.body end - def test_override_view_paths_with_custom_resolver - resolver_class = Class.new(ActionView::PathResolver) do + def test_decorate_view_paths_with_custom_resolver + decorator_class = Class.new(ActionView::PathResolver) do def initialize(path_set) @path_set = path_set end @@ -140,7 +140,7 @@ class ViewLoadPathsTest < ActionController::TestCase def find_all(*args) @path_set.find_all(*args).collect do |template| ::ActionView::Template.new( - "Customized body", + "Decorated body", template.identifier, template.handler, { @@ -152,12 +152,12 @@ class ViewLoadPathsTest < ActionController::TestCase end end - resolver = resolver_class.new(TestController.view_paths) - TestController.view_paths = ActionView::PathSet.new.push(resolver) + decorator = decorator_class.new(TestController.view_paths) + TestController.view_paths = ActionView::PathSet.new.push(decorator) get :hello_world assert_response :success - assert_equal "Customized body", @response.body + assert_equal "Decorated body", @response.body end def test_inheritance diff --git a/actionpack/test/dispatch/prefix_generation_test.rb b/actionpack/test/dispatch/prefix_generation_test.rb index 18f28deee4..b28a058250 100644 --- a/actionpack/test/dispatch/prefix_generation_test.rb +++ b/actionpack/test/dispatch/prefix_generation_test.rb @@ -69,6 +69,7 @@ module TestGenerationPrefix # force draw RailsApplication.routes + RailsApplication.routes.define_mounted_helper(:main_app) class ::InsideEngineGeneratingController < ActionController::Base include BlogEngine.routes.url_helpers diff --git a/actionpack/test/dispatch/response_test.rb b/actionpack/test/dispatch/response_test.rb index 6f38714b2e..5abbaf74fe 100644 --- a/actionpack/test/dispatch/response_test.rb +++ b/actionpack/test/dispatch/response_test.rb @@ -33,22 +33,6 @@ class ResponseTest < ActiveSupport::TestCase }, headers) end - test "streaming block" do - @response.body = Proc.new do |response, output| - 5.times { |n| output.write(n) } - end - - status, headers, body = @response.to_a - assert_equal 200, status - assert_equal({ - "Content-Type" => "text/html; charset=utf-8" - }, headers) - - parts = [] - body.each { |part| parts << part.to_s } - assert_equal ["0", "1", "2", "3", "4"], parts - end - test "content type" do [204, 304].each do |c| @response.status = c.to_s diff --git a/actionpack/test/dispatch/static_test.rb b/actionpack/test/dispatch/static_test.rb index 655745a848..2ebbed4414 100644 --- a/actionpack/test/dispatch/static_test.rb +++ b/actionpack/test/dispatch/static_test.rb @@ -47,35 +47,4 @@ class StaticTest < ActiveSupport::TestCase end include StaticTests -end - -class MultipleDirectorisStaticTest < ActiveSupport::TestCase - DummyApp = lambda { |env| - [200, {"Content-Type" => "text/plain"}, ["Hello, World!"]] - } - App = ActionDispatch::Static.new(DummyApp, - { "/" => "#{FIXTURE_LOAD_PATH}/public", - "/blog" => "#{FIXTURE_LOAD_PATH}/blog_public", - "/foo" => "#{FIXTURE_LOAD_PATH}/non_existing_dir" - }) - - def setup - @app = App - end - - include StaticTests - - test "serves files from other mounted directories" do - assert_html "/blog/index.html", get("/blog/index.html") - assert_html "/blog/index.html", get("/blog/index") - assert_html "/blog/index.html", get("/blog/") - - assert_html "/blog/blog.html", get("/blog/blog/") - assert_html "/blog/blog.html", get("/blog/blog.html") - assert_html "/blog/blog.html", get("/blog/blog") - - assert_html "/blog/subdir/index.html", get("/blog/subdir/index.html") - assert_html "/blog/subdir/index.html", get("/blog/subdir/") - assert_html "/blog/subdir/index.html", get("/blog/subdir") - end -end +end
\ No newline at end of file diff --git a/actionpack/test/fixtures/layouts/streaming.erb b/actionpack/test/fixtures/layouts/streaming.erb new file mode 100644 index 0000000000..d3f896a6ca --- /dev/null +++ b/actionpack/test/fixtures/layouts/streaming.erb @@ -0,0 +1,4 @@ +<%= yield :header -%> +<%= yield -%> +<%= yield :footer -%> +<%= yield(:unknown).presence || "." -%>
\ No newline at end of file diff --git a/actionpack/test/fixtures/sprockets/app/images/logo.png b/actionpack/test/fixtures/sprockets/app/images/logo.png Binary files differnew file mode 100644 index 0000000000..d5edc04e65 --- /dev/null +++ b/actionpack/test/fixtures/sprockets/app/images/logo.png diff --git a/actionpack/test/fixtures/test/nested_streaming.erb b/actionpack/test/fixtures/test/nested_streaming.erb new file mode 100644 index 0000000000..55525e0c92 --- /dev/null +++ b/actionpack/test/fixtures/test/nested_streaming.erb @@ -0,0 +1,3 @@ +<%- content_for :header do -%>?<%- end -%> +<%= render :template => "test/streaming" %> +?
\ No newline at end of file diff --git a/actionpack/test/fixtures/test/streaming.erb b/actionpack/test/fixtures/test/streaming.erb new file mode 100644 index 0000000000..fb9b8b1ade --- /dev/null +++ b/actionpack/test/fixtures/test/streaming.erb @@ -0,0 +1,3 @@ +<%- provide :header do -%>Yes, <%- end -%> +this works +<%- content_for :footer, " like a charm" -%> diff --git a/actionpack/test/fixtures/test/streaming_buster.erb b/actionpack/test/fixtures/test/streaming_buster.erb new file mode 100644 index 0000000000..4221d56dcc --- /dev/null +++ b/actionpack/test/fixtures/test/streaming_buster.erb @@ -0,0 +1,3 @@ +<%= yield :foo -%> +This won't look +<% provide :unknown, " good." -%> diff --git a/actionpack/test/template/asset_tag_helper_test.rb b/actionpack/test/template/asset_tag_helper_test.rb index 1bf748af14..4a93def5a8 100644 --- a/actionpack/test/template/asset_tag_helper_test.rb +++ b/actionpack/test/template/asset_tag_helper_test.rb @@ -477,15 +477,6 @@ class AssetTagHelperTest < ActionView::TestCase assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png") end - def test_env_asset_path - @controller.config.asset_path = "/assets%s" - def @controller.env; @_env ||= {} end - @controller.env["action_dispatch.asset_path"] = "/omg%s" - - expected_path = "/assets/omg/images/rails.png" - assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png") - end - def test_proc_asset_id @controller.config.asset_path = Proc.new do |asset_path| "/assets.v12345#{asset_path}" @@ -495,20 +486,6 @@ class AssetTagHelperTest < ActionView::TestCase assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png") end - def test_env_proc_asset_path - @controller.config.asset_path = Proc.new do |asset_path| - "/assets.v12345#{asset_path}" - end - - def @controller.env; @_env ||= {} end - @controller.env["action_dispatch.asset_path"] = Proc.new do |asset_path| - "/omg#{asset_path}" - end - - expected_path = "/assets.v12345/omg/images/rails.png" - assert_equal %(<img alt="Rails" src="#{expected_path}" />), image_tag("rails.png") - end - def test_image_tag_interpreting_email_cid_correctly # An inline image has no need for an alt tag to be automatically generated from the cid: assert_equal '<img src="cid:thi%25%25sis@acontentid" />', image_tag("cid:thi%25%25sis@acontentid") diff --git a/actionpack/test/template/capture_helper_test.rb b/actionpack/test/template/capture_helper_test.rb index 03050485fa..a9a36e6e6b 100644 --- a/actionpack/test/template/capture_helper_test.rb +++ b/actionpack/test/template/capture_helper_test.rb @@ -4,7 +4,7 @@ class CaptureHelperTest < ActionView::TestCase def setup super @av = ActionView::Base.new - @_content_for = Hash.new {|h,k| h[k] = "" } + @_view_flow = ActionView::OutputFlow.new end def test_capture_captures_the_temporary_output_buffer_in_its_block @@ -45,6 +45,20 @@ class CaptureHelperTest < ActionView::TestCase assert ! content_for?(:something_else) end + def test_provide + assert !content_for?(:title) + provide :title, "hi" + assert content_for?(:title) + assert_equal "hi", @_view_flow.get(:title) + provide :title, "<p>title</p>" + assert_equal "hi<p>title</p>", @_view_flow.get(:title) + + @_view_flow = ActionView::OutputFlow.new + provide :title, "hi" + provide :title, "<p>title</p>".html_safe + assert_equal "hi<p>title</p>", @_view_flow.get(:title) + end + def test_with_output_buffer_swaps_the_output_buffer_given_no_argument assert_nil @av.output_buffer buffer = @av.with_output_buffer do diff --git a/actionpack/test/template/lookup_context_test.rb b/actionpack/test/template/lookup_context_test.rb index 8d063e66b0..ff94cba59f 100644 --- a/actionpack/test/template/lookup_context_test.rb +++ b/actionpack/test/template/lookup_context_test.rb @@ -180,16 +180,6 @@ class LookupContextTest < ActiveSupport::TestCase assert_not_equal template, old_template end - - test "data can be stored in cached templates" do - template = @lookup_context.find("hello_world", %w(test)) - template.data["cached"] = "data" - assert_equal "Hello world!", template.source - - template = @lookup_context.find("hello_world", %w(test)) - assert_equal "data", template.data["cached"] - assert_equal "Hello world!", template.source - end end class LookupContextWithFalseCaching < ActiveSupport::TestCase @@ -235,21 +225,6 @@ class LookupContextWithFalseCaching < ActiveSupport::TestCase template = @lookup_context.find("foo", %w(test), true) assert_equal "Foo", template.source end - - test "data can be stored as long as template was not updated" do - template = @lookup_context.find("foo", %w(test), true) - template.data["cached"] = "data" - assert_equal "Foo", template.source - - template = @lookup_context.find("foo", %w(test), true) - assert_equal "data", template.data["cached"] - assert_equal "Foo", template.source - - @resolver.hash["test/_foo.erb"][1] = Time.now.utc - template = @lookup_context.find("foo", %w(test), true) - assert_nil template.data["cached"] - assert_equal "Foo", template.source - end end class TestMissingTemplate < ActiveSupport::TestCase diff --git a/actionpack/test/template/number_helper_test.rb b/actionpack/test/template/number_helper_test.rb index c8d50ebf75..23a7e17e65 100644 --- a/actionpack/test/template/number_helper_test.rb +++ b/actionpack/test/template/number_helper_test.rb @@ -32,6 +32,7 @@ class NumberHelperTest < ActionView::TestCase assert_equal("555-1234", number_to_phone(5551234)) assert_equal("800-555-1212", number_to_phone(8005551212)) assert_equal("(800) 555-1212", number_to_phone(8005551212, {:area_code => true})) + assert_equal("", number_to_phone("", {:area_code => true})) assert_equal("800 555 1212", number_to_phone(8005551212, {:delimiter => " "})) assert_equal("(800) 555-1212 x 123", number_to_phone(8005551212, {:area_code => true, :extension => 123})) assert_equal("800-555-1212", number_to_phone(8005551212, :extension => " ")) diff --git a/actionpack/test/template/render_test.rb b/actionpack/test/template/render_test.rb index bce11c4dd3..d4e912c410 100644 --- a/actionpack/test/template/render_test.rb +++ b/actionpack/test/template/render_test.rb @@ -227,20 +227,11 @@ module RenderTestCases "@output_buffer << 'source: #{template.source.inspect}'\n" end - WithViewHandler = lambda do |template, view| - %'"#{template.class} #{view.class}"' - end - def test_render_inline_with_render_from_to_proc ActionView::Template.register_template_handler :ruby_handler, :source.to_proc assert_equal '3', @view.render(:inline => "(1 + 2).to_s", :type => :ruby_handler) end - def test_render_inline_with_template_handler_with_view - ActionView::Template.register_template_handler :with_view, WithViewHandler - assert_equal 'ActionView::Template ActionView::Base', @view.render(:inline => "Hello, World!", :type => :with_view) - end - def test_render_inline_with_compilable_custom_type ActionView::Template.register_template_handler :foo, CustomHandler assert_equal 'source: "Hello, World!"', @view.render(:inline => "Hello, World!", :type => :foo) diff --git a/actionpack/test/template/sprockets_helper_test.rb b/actionpack/test/template/sprockets_helper_test.rb index 67aee86d02..8d3be09a4f 100644 --- a/actionpack/test/template/sprockets_helper_test.rb +++ b/actionpack/test/template/sprockets_helper_test.rb @@ -1,5 +1,8 @@ require 'abstract_unit' require 'sprockets' +require 'mocha' + +module Rails; end class SprocketsHelperTest < ActionView::TestCase tests ActionView::Helpers::SprocketsHelper @@ -22,6 +25,12 @@ class SprocketsHelperTest < ActionView::TestCase @assets = Sprockets::Environment.new @assets.paths << FIXTURES.join("sprockets/app/javascripts") @assets.paths << FIXTURES.join("sprockets/app/stylesheets") + @assets.paths << FIXTURES.join("sprockets/app/images") + + application = Object.new + Rails.stubs(:application).returns(application) + application.stubs(:config).returns(config) + application.stubs(:assets).returns(@assets) config.perform_caching = true end @@ -30,22 +39,40 @@ class SprocketsHelperTest < ActionView::TestCase "http://www.example.com" end + test "asset path" do + assert_equal "/assets/logo-9c0a079bdd7701d7e729bd956823d153.png", + asset_path("logo.png") + + assert_equal "/images/logo", + asset_path("/images/logo") + assert_equal "/images/logo.gif", + asset_path("/images/logo.gif") + + assert_equal "/dir/audio", + asset_path("/dir/audio") + + assert_equal "http://www.example.com/video/play", + asset_path("http://www.example.com/video/play") + assert_equal "http://www.example.com/video/play.mp4", + asset_path("http://www.example.com/video/play.mp4") + end + test "javascript path" do assert_equal "/assets/application-d41d8cd98f00b204e9800998ecf8427e.js", - sprockets_javascript_path(:application) + asset_path(:application, "js") assert_equal "/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js", - sprockets_javascript_path("xmlhr") + asset_path("xmlhr", "js") assert_equal "/assets/dir/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js", - sprockets_javascript_path("dir/xmlhr.js") + asset_path("dir/xmlhr.js", "js") assert_equal "/dir/xmlhr.js", - sprockets_javascript_path("/dir/xmlhr") + asset_path("/dir/xmlhr", "js") - assert_equal "http://www.railsapplication.com/js/xmlhr", - sprockets_javascript_path("http://www.railsapplication.com/js/xmlhr") - assert_equal "http://www.railsapplication.com/js/xmlhr.js", - sprockets_javascript_path("http://www.railsapplication.com/js/xmlhr.js") + assert_equal "http://www.example.com/js/xmlhr", + asset_path("http://www.example.com/js/xmlhr", "js") + assert_equal "http://www.example.com/js/xmlhr.js", + asset_path("http://www.example.com/js/xmlhr.js", "js") end test "javascript include tag" do @@ -56,25 +83,21 @@ class SprocketsHelperTest < ActionView::TestCase sprockets_javascript_include_tag("xmlhr") assert_equal '<script src="/assets/xmlhr-d41d8cd98f00b204e9800998ecf8427e.js" type="text/javascript"></script>', sprockets_javascript_include_tag("xmlhr.js") - assert_equal '<script src="http://www.railsapplication.com/xmlhr" type="text/javascript"></script>', - sprockets_javascript_include_tag("http://www.railsapplication.com/xmlhr") + assert_equal '<script src="http://www.example.com/xmlhr" type="text/javascript"></script>', + sprockets_javascript_include_tag("http://www.example.com/xmlhr") end test "stylesheet path" do - assert_equal "/assets/application-d41d8cd98f00b204e9800998ecf8427e.css", - sprockets_stylesheet_path(:application) - - assert_equal "/assets/style-d41d8cd98f00b204e9800998ecf8427e.css", - sprockets_stylesheet_path("style") - assert_equal "/assets/dir/style-d41d8cd98f00b204e9800998ecf8427e.css", - sprockets_stylesheet_path("dir/style.css") - assert_equal "/dir/style.css", - sprockets_stylesheet_path("/dir/style.css") - - assert_equal "http://www.railsapplication.com/css/style", - sprockets_stylesheet_path("http://www.railsapplication.com/css/style") - assert_equal "http://www.railsapplication.com/css/style.css", - sprockets_stylesheet_path("http://www.railsapplication.com/css/style.css") + assert_equal "/assets/application-d41d8cd98f00b204e9800998ecf8427e.css", asset_path(:application, "css") + + assert_equal "/assets/style-d41d8cd98f00b204e9800998ecf8427e.css", asset_path("style", "css") + assert_equal "/assets/dir/style-d41d8cd98f00b204e9800998ecf8427e.css", asset_path("dir/style.css", "css") + assert_equal "/dir/style.css", asset_path("/dir/style.css", "css") + + assert_equal "http://www.example.com/css/style", + asset_path("http://www.example.com/css/style", "css") + assert_equal "http://www.example.com/css/style.css", + asset_path("http://www.example.com/css/style.css", "css") end test "stylesheet link tag" do @@ -86,8 +109,8 @@ class SprocketsHelperTest < ActionView::TestCase assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="screen" rel="stylesheet" type="text/css" />', sprockets_stylesheet_link_tag("style.css") - assert_equal '<link href="http://www.railsapplication.com/style.css" media="screen" rel="stylesheet" type="text/css" />', - sprockets_stylesheet_link_tag("http://www.railsapplication.com/style.css") + assert_equal '<link href="http://www.example.com/style.css" media="screen" rel="stylesheet" type="text/css" />', + sprockets_stylesheet_link_tag("http://www.example.com/style.css") assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="all" rel="stylesheet" type="text/css" />', sprockets_stylesheet_link_tag("style", :media => "all") assert_equal '<link href="/assets/style-d41d8cd98f00b204e9800998ecf8427e.css" media="print" rel="stylesheet" type="text/css" />', diff --git a/actionpack/test/template/streaming_render_test.rb b/actionpack/test/template/streaming_render_test.rb new file mode 100644 index 0000000000..4d69081570 --- /dev/null +++ b/actionpack/test/template/streaming_render_test.rb @@ -0,0 +1,105 @@ +# encoding: utf-8 +require 'abstract_unit' +require 'controller/fake_models' + +class TestController < ActionController::Base +end + +class FiberedTest < ActiveSupport::TestCase + def setup + view_paths = ActionController::Base.view_paths + @assigns = { :secret => 'in the sauce' } + @view = ActionView::Base.new(view_paths, @assigns) + @controller_view = TestController.new.view_context + end + + def buffered_render(options) + body = @view.render_body(options) + string = "" + body.each do |piece| + string << piece + end + string + end + + def test_streaming_works + content = [] + body = @view.render_body(:template => "test/hello_world.erb", :layout => "layouts/yield") + + body.each do |piece| + content << piece + end + + assert_equal "<title>", content[0] + assert_equal "", content[1] + assert_equal "</title>\n", content[2] + assert_equal "Hello world!", content[3] + assert_equal "\n", content[4] + end + + def test_render_file + assert_equal "Hello world!", buffered_render(:file => "test/hello_world.erb") + end + + def test_render_file_with_locals + locals = { :secret => 'in the sauce' } + assert_equal "The secret is in the sauce\n", buffered_render(:file => "test/render_file_with_locals.erb", :locals => locals) + end + + def test_render_partial + assert_equal "only partial", buffered_render(:partial => "test/partial_only") + end + + def test_render_inline + assert_equal "Hello, World!", buffered_render(:inline => "Hello, World!") + end + + def test_render_without_layout + assert_equal "Hello world!", buffered_render(:template => "test/hello_world") + end + + def test_render_with_layout + assert_equal %(<title></title>\nHello world!\n), + buffered_render(:template => "test/hello_world.erb", :layout => "layouts/yield") + end + + def test_render_with_layout_which_has_render_inline + assert_equal %(welcome\nHello world!\n), + buffered_render(:template => "test/hello_world.erb", :layout => "layouts/yield_with_render_inline_inside") + end + + def test_render_with_layout_which_renders_another_partial + assert_equal %(partial html\nHello world!\n), + buffered_render(:template => "test/hello_world.erb", :layout => "layouts/yield_with_render_partial_inside") + end + + def test_render_with_nested_layout + assert_equal %(<title>title</title>\n\n<div id="column">column</div>\n<div id="content">content</div>\n), + buffered_render(:template => "test/nested_layout.erb", :layout => "layouts/yield") + end + + def test_render_with_file_in_layout + assert_equal %(\n<title>title</title>\n\n), + buffered_render(:template => "test/layout_render_file.erb") + end + + def test_render_with_handler_without_streaming_support + assert_match "<p>This is grand!</p>", buffered_render(:template => "test/hello") + end + + def test_render_with_streaming_multiple_yields_provide_and_content_for + assert_equal "Yes, \nthis works\n like a charm.", + buffered_render(:template => "test/streaming", :layout => "layouts/streaming") + end + + def test_render_with_streaming_with_fake_yields_and_streaming_buster + assert_equal "This won't look\n good.", + buffered_render(:template => "test/streaming_buster", :layout => "layouts/streaming") + end + + def test_render_with_nested_streaming_multiple_yields_provide_and_content_for + assert_equal "?Yes, \n\nthis works\n\n? like a charm.", + buffered_render(:template => "test/nested_streaming", :layout => "layouts/streaming") + end + +end if defined?(Fiber)
\ No newline at end of file diff --git a/actionpack/test/template/template_test.rb b/actionpack/test/template/template_test.rb index 3432a02c3c..5c655d5b69 100644 --- a/actionpack/test/template/template_test.rb +++ b/actionpack/test/template/template_test.rb @@ -113,44 +113,6 @@ class TestERBTemplate < ActiveSupport::TestCase end end - def test_template_expire_sets_the_timestamp_to_1970 - @template = new_template("Hello", :updated_at => Time.utc(2010)) - assert_equal Time.utc(2010), @template.updated_at - @template.expire! - assert_equal Time.utc(1970), @template.updated_at - end - - def test_template_rerender_renders_a_template_like_self - @template = new_template("Hello", :virtual_path => "test/foo_bar") - @context.expects(:render).with(:template => "test/foo_bar").returns("template") - assert_equal "template", @template.rerender(@context) - end - - def test_template_rerender_renders_a_root_template_like_self - @template = new_template("Hello", :virtual_path => "foo_bar") - @context.expects(:render).with(:template => "foo_bar").returns("template") - assert_equal "template", @template.rerender(@context) - end - - def test_template_rerender_renders_a_partial_like_self - @template = new_template("Hello", :virtual_path => "test/_foo_bar") - @context.expects(:render).with(:partial => "test/foo_bar").returns("partial") - assert_equal "partial", @template.rerender(@context) - end - - def test_template_rerender_renders_a_root_partial_like_self - @template = new_template("Hello", :virtual_path => "_foo_bar") - @context.expects(:render).with(:partial => "foo_bar").returns("partial") - assert_equal "partial", @template.rerender(@context) - end - - def test_rerender_raises_an_error_without_virtual_path - @template = new_template("Hello", :virtual_path => nil) - assert_raise RuntimeError do - @template.rerender(@context) - end - end - if "ruby".encoding_aware? def test_resulting_string_is_utf8 @template = new_template |