aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack
diff options
context:
space:
mode:
Diffstat (limited to 'actionpack')
-rw-r--r--actionpack/actionpack.gemspec2
-rw-r--r--actionpack/lib/abstract_controller/helpers.rb22
-rw-r--r--actionpack/lib/abstract_controller/rendering.rb27
-rw-r--r--actionpack/lib/action_controller.rb1
-rw-r--r--actionpack/lib/action_controller/base.rb1
-rw-r--r--actionpack/lib/action_controller/metal.rb2
-rw-r--r--actionpack/lib/action_controller/metal/data_streaming.rb145
-rw-r--r--actionpack/lib/action_controller/metal/helpers.rb52
-rw-r--r--actionpack/lib/action_controller/metal/mime_responds.rb2
-rw-r--r--actionpack/lib/action_controller/metal/renderers.rb6
-rw-r--r--actionpack/lib/action_controller/metal/rendering.rb11
-rw-r--r--actionpack/lib/action_controller/metal/streaming.rb176
-rw-r--r--actionpack/lib/action_controller/railtie.rb2
-rw-r--r--actionpack/lib/action_controller/railties/paths.rb8
-rw-r--r--actionpack/lib/action_dispatch/http/response.rb109
-rw-r--r--actionpack/lib/action_dispatch/middleware/flash.rb49
-rw-r--r--actionpack/lib/action_dispatch/middleware/show_exceptions.rb2
-rw-r--r--actionpack/lib/action_dispatch/middleware/static.rb43
-rw-r--r--actionpack/lib/action_dispatch/routing/route_set.rb3
-rw-r--r--actionpack/lib/action_dispatch/testing/assertions/response.rb2
-rw-r--r--actionpack/lib/action_view.rb11
-rw-r--r--actionpack/lib/action_view/base.rb14
-rw-r--r--actionpack/lib/action_view/buffers.rb43
-rw-r--r--actionpack/lib/action_view/context.rb1
-rw-r--r--actionpack/lib/action_view/flows.rb79
-rw-r--r--actionpack/lib/action_view/helpers/asset_paths.rb80
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helper.rb28
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_include_tag.rb5
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/asset_paths.rb159
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/javascript_tag_helpers.rb23
-rw-r--r--actionpack/lib/action_view/helpers/asset_tag_helpers/stylesheet_tag_helpers.rb19
-rw-r--r--actionpack/lib/action_view/helpers/capture_helper.rb17
-rw-r--r--actionpack/lib/action_view/helpers/date_helper.rb10
-rw-r--r--actionpack/lib/action_view/helpers/form_helper.rb7
-rw-r--r--actionpack/lib/action_view/helpers/number_helper.rb8
-rw-r--r--actionpack/lib/action_view/helpers/record_tag_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/sanitize_helper.rb2
-rw-r--r--actionpack/lib/action_view/helpers/sprockets_helper.rb74
-rw-r--r--actionpack/lib/action_view/helpers/text_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/translation_helper.rb6
-rw-r--r--actionpack/lib/action_view/helpers/url_helper.rb6
-rw-r--r--actionpack/lib/action_view/renderer/partial_renderer.rb4
-rw-r--r--actionpack/lib/action_view/renderer/streaming_template_renderer.rb130
-rw-r--r--actionpack/lib/action_view/renderer/template_renderer.rb30
-rw-r--r--actionpack/lib/action_view/rendering.rb35
-rw-r--r--actionpack/lib/action_view/template.rb37
-rw-r--r--actionpack/lib/action_view/template/handlers/erb.rb20
-rw-r--r--actionpack/lib/sprockets/railtie.rb100
-rw-r--r--actionpack/test/controller/flash_test.rb17
-rw-r--r--actionpack/test/controller/new_base/render_once_test.rb86
-rw-r--r--actionpack/test/controller/new_base/render_streaming_test.rb101
-rw-r--r--actionpack/test/controller/new_base/render_test.rb3
-rw-r--r--actionpack/test/controller/view_paths_test.rb12
-rw-r--r--actionpack/test/dispatch/prefix_generation_test.rb1
-rw-r--r--actionpack/test/dispatch/response_test.rb16
-rw-r--r--actionpack/test/dispatch/static_test.rb33
-rw-r--r--actionpack/test/fixtures/layouts/streaming.erb4
-rw-r--r--actionpack/test/fixtures/sprockets/app/images/logo.pngbin0 -> 6646 bytes
-rw-r--r--actionpack/test/fixtures/test/nested_streaming.erb3
-rw-r--r--actionpack/test/fixtures/test/streaming.erb3
-rw-r--r--actionpack/test/fixtures/test/streaming_buster.erb3
-rw-r--r--actionpack/test/template/asset_tag_helper_test.rb23
-rw-r--r--actionpack/test/template/capture_helper_test.rb16
-rw-r--r--actionpack/test/template/lookup_context_test.rb25
-rw-r--r--actionpack/test/template/number_helper_test.rb1
-rw-r--r--actionpack/test/template/render_test.rb9
-rw-r--r--actionpack/test/template/sprockets_helper_test.rb75
-rw-r--r--actionpack/test/template/streaming_render_test.rb105
-rw-r--r--actionpack/test/template/template_test.rb38
69 files changed, 1342 insertions, 857 deletions
diff --git a/actionpack/actionpack.gemspec b/actionpack/actionpack.gemspec
index d3c66800d9..f771737779 100644
--- a/actionpack/actionpack.gemspec
+++ b/actionpack/actionpack.gemspec
@@ -21,7 +21,7 @@ Gem::Specification.new do |s|
s.add_dependency('activemodel', version)
s.add_dependency('rack-cache', '~> 1.0.0')
s.add_dependency('builder', '~> 3.0.0')
- s.add_dependency('i18n', '~> 0.5.0')
+ s.add_dependency('i18n', '~> 0.6.0beta1')
s.add_dependency('rack', '~> 1.2.1')
s.add_dependency('rack-test', '~> 0.5.7')
s.add_dependency('rack-mount', '~> 0.7.1')
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/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 f6b2d4f3f4..9bc847a1ab 100644
--- a/actionpack/lib/action_view/helpers/asset_tag_helper.rb
+++ b/actionpack/lib/action_view/helpers/asset_tag_helper.rb
@@ -57,7 +57,7 @@ module ActionView
# +asset_host+ to a proc like this:
#
# ActionController::Base.asset_host = Proc.new { |source|
- # "http://assets#{source.hash % 2 + 1}.example.com"
+ # "http://assets#{Digest::MD5.hexdigest(source).to_i(16) % 2 + 1}.example.com"
# }
# image_tag("rails.png")
# # => <img alt="Rails" src="http://assets1.example.com/images/rails.png?1230601161" />
@@ -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 => "&pound;", :separator => ",", :delimiter => "")
# # => &pound;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/translation_helper.rb b/actionpack/lib/action_view/helpers/translation_helper.rb
index 59e6ce878f..26ebae6546 100644
--- a/actionpack/lib/action_view/helpers/translation_helper.rb
+++ b/actionpack/lib/action_view/helpers/translation_helper.rb
@@ -5,7 +5,7 @@ module I18n
class ExceptionHandler
include Module.new {
def call(exception, locale, key, options)
- exception.is_a?(MissingTranslationData) ? super.html_safe : super
+ exception.is_a?(MissingTranslation) ? super.html_safe : super
end
}
end
@@ -17,8 +17,8 @@ module ActionView
module TranslationHelper
# Delegates to I18n#translate but also performs three additional functions.
#
- # First, it'll pass the :rescue_format => :html option to I18n so that any caught
- # MissingTranslationData exceptions will be turned into inline spans that
+ # First, it'll pass the :rescue_format => :html option to I18n so that any
+ # thrown MissingTranslation messages will be turned into inline spans that
#
# * have a "translation-missing" class set,
# * contain the missing key as a title attribute and
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
new file mode 100644
index 0000000000..d5edc04e65
--- /dev/null
+++ b/actionpack/test/fixtures/sprockets/app/images/logo.png
Binary files differ
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&lt;p&gt;title&lt;/p&gt;", @_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