Add asset_url helper and refactor the asset paths so that asset hosts can be used during asset precompilation.

This commit is contained in:
Chris Eppstein
2011-06-13 18:25:05 -07:00
parent 729d8688af
commit 1380a6180a
5 changed files with 130 additions and 49 deletions

View File

@@ -6,7 +6,7 @@ module ActionView
class AssetPaths #:nodoc:
attr_reader :config, :controller
def initialize(config, controller)
def initialize(config, controller = nil)
@config = config
@controller = controller
end
@@ -21,15 +21,17 @@ module ActionView
source = rewrite_extension(source, dir, ext) if ext
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 = rewrite_relative_url_root(source, relative_url_root) if has_request?
source = rewrite_host_and_protocol(source) if include_host
source
end
# Return the filesystem path for the source
def compute_source_path(source, dir, ext)
source = rewrite_extension(source, dir, ext) if ext
File.join(config.assets_dir, dir, source)
end
def is_uri?(path)
path =~ %r{^[-a-z]+://|^cid:|^//}
end
@@ -48,13 +50,14 @@ module ActionView
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
def has_request?
controller.respond_to?(:request)
end
def rewrite_host_and_protocol(source)
host = compute_asset_host(source)
if has_request && host && !is_uri?(host)
host = "#{controller.request.protocol}#{host}"
end
"#{host}#{source}"
host = "//#{host}" if host && !is_uri?(host)
host.nil? ? source : "#{host}#{source}"
end
# Pick an asset host for this source. Returns +nil+ if no host is set,
@@ -63,21 +66,49 @@ module ActionView
# 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)
if host = asset_host_config
if host.respond_to?(:call)
args = [source]
arity = arity_of(host)
if arity > 1 && !has_request?
raise ActionController::RoutingError, "This asset host cannot be computed without a request in scope. Remove the second argument to your asset_host Proc if you do not need the request."
end
args << current_request if (arity > 1 || arity < 0) && has_request?
host.call(*args)
else
(host =~ /%d/) ? host % (source.hash % 4) : host
end
end
end
end
def relative_url_root
if controller.respond_to?(:config) && controller.config
controller.config.relative_url_root
elsif config.respond_to?(:action_controller) && config.action_controller
config.action_controller.relative_url_root
elsif Rails.respond_to?(:application) && Rails.application.config
Rails.application.config.action_controller.relative_url_root
end
end
def asset_host_config
if config.respond_to?(:asset_host)
config.asset_host
elsif Rails.respond_to?(:application)
Rails.application.config.action_controller.asset_host
end
end
# Returns the current request if one exists.
def current_request
controller.request if has_request?
end
# Returns the arity of a callable
def arity_of(callable)
callable.respond_to?(:arity) ? callable.arity : callable.method(:call).arity
end
end
end
end

View File

@@ -60,12 +60,16 @@ module ActionView
private
def path_to_asset(source, include_host = true)
asset_paths.compute_public_path(source, asset_name.to_s.pluralize, extension, include_host)
def path_to_asset(source)
asset_paths.compute_public_path(source, asset_name.to_s.pluralize, extension)
end
def path_to_asset_source(source)
asset_paths.compute_source_path(source, asset_name.to_s.pluralize, extension)
end
def compute_paths(*args)
expand_sources(*args).collect { |source| asset_paths.compute_public_path(source, asset_name.pluralize, extension, false) }
expand_sources(*args).collect { |source| path_to_asset_source(source) }
end
def expand_sources(sources, recursive)
@@ -92,7 +96,7 @@ module ActionView
def ensure_sources!(sources)
sources.each do |source|
asset_file_path!(path_to_asset(source, false))
asset_file_path!(path_to_asset_source(source))
end
end
@@ -123,19 +127,14 @@ module ActionView
# Set mtime to the latest of the combined files to allow for
# consistent ETag without a shared filesystem.
mt = asset_paths.map { |p| File.mtime(asset_file_path(p)) }.max
mt = asset_paths.map { |p| File.mtime(asset_file_path!(p)) }.max
File.utime(mt, mt, joined_asset_path)
end
def asset_file_path(path)
File.join(config.assets_dir, path.split('?').first)
end
def asset_file_path!(path, error_if_file_is_uri = false)
if asset_paths.is_uri?(path)
def asset_file_path!(absolute_path, error_if_file_is_uri = false)
if asset_paths.is_uri?(absolute_path)
raise(Errno::ENOENT, "Asset file #{path} is uri and cannot be merged into single file") if error_if_file_is_uri
else
absolute_path = asset_file_path(path)
raise(Errno::ENOENT, "Asset file not found at '#{absolute_path}'" ) unless File.exist?(absolute_path)
return absolute_path
end

View File

@@ -59,23 +59,28 @@ module Sprockets
end.join("\n").html_safe
end
def asset_path(source, default_ext = nil, body = false)
source = source.logical_path if source.respond_to?(:logical_path)
path = asset_paths.compute_public_path(source, 'assets', default_ext)
body ? "#{path}?body=1" : path
end
private
def debug_assets?
params[:debug_assets] == '1' ||
params[:debug_assets] == 'true'
end
def asset_path(source, default_ext = nil, body = false)
source = source.logical_path if source.respond_to?(:logical_path)
path = asset_paths.compute_public_path(source, 'assets', default_ext, true)
body ? "#{path}?body=1" : path
end
class AssetPaths < ActionView::Helpers::AssetPaths #:nodoc:
def compute_public_path(source, dir, ext=nil, include_host=true)
super(source, 'assets', ext, include_host)
end
# Return the filesystem path for the source
def compute_source_path(source, ext)
asset_for(source, ext)
end
def asset_for(source, ext)
source = source.to_s
return nil if is_uri?(source)
@@ -105,7 +110,7 @@ module Sprockets
# 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
@config ? @config.perform_caching : Rails.application.config.action_controller.perform_caching
end
end
end

View File

@@ -1115,14 +1115,14 @@ class AssetTagHelperNonVhostTest < ActionView::TestCase
assert_match(%r(http://a[0123].example.com/collaboration/hieraki/images/xml.png), image_path('xml.png'))
end
def test_asset_host_without_protocol_should_use_request_protocol
def test_asset_host_without_protocol_should_be_protocol_relative
@controller.config.asset_host = 'a.example.com'
assert_equal 'gopher://a.example.com/collaboration/hieraki/images/xml.png', image_path('xml.png')
assert_equal '//a.example.com/collaboration/hieraki/images/xml.png', image_path('xml.png')
end
def test_asset_host_without_protocol_should_use_request_protocol_even_if_path_present
def test_asset_host_without_protocol_should_be_protocol_relative_even_if_path_present
@controller.config.asset_host = 'a.example.com/files/go/here'
assert_equal 'gopher://a.example.com/files/go/here/collaboration/hieraki/images/xml.png', image_path('xml.png')
assert_equal '//a.example.com/files/go/here/collaboration/hieraki/images/xml.png', image_path('xml.png')
end
def test_assert_css_and_js_of_the_same_name_return_correct_extension

View File

@@ -33,18 +33,21 @@ class SprocketsHelperTest < ActionView::TestCase
Rails.stubs(:application).returns(application)
application.stubs(:config).returns(config)
application.stubs(:assets).returns(@assets)
config.perform_caching = true
@config = config
@config.action_controller ||= ActiveSupport::InheritableOptions.new
@config.perform_caching = true
end
def url_for(*args)
"http://www.example.com"
end
test "asset path" do
test "asset_path" do
assert_equal "/assets/logo-9c0a079bdd7701d7e729bd956823d153.png",
asset_path("logo.png")
end
test "asset_path with root relative assets" do
assert_equal "/images/logo",
asset_path("/images/logo")
assert_equal "/images/logo.gif",
@@ -52,13 +55,56 @@ class SprocketsHelperTest < ActionView::TestCase
assert_equal "/dir/audio",
asset_path("/dir/audio")
end
test "asset_path with absolute urls" do
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 "with a simple asset host the url should be protocol relative" do
@controller.config.asset_host = "assets-%d.example.com"
assert_match %r{//assets-\d.example.com/assets/logo-[0-9a-f]+.png},
asset_path("logo.png")
end
test "With a proc asset host that returns no protocol the url should be protocol relative" do
@controller.config.asset_host = Proc.new do |asset|
"assets-999.example.com"
end
assert_match %r{//assets-999.example.com/assets/logo-[0-9a-f]+.png},
asset_path("logo.png")
end
test "with a proc asset host that returns a protocol the url use it" do
@controller.config.asset_host = Proc.new do |asset|
"http://assets-999.example.com"
end
assert_match %r{http://assets-999.example.com/assets/logo-[0-9a-f]+.png},
asset_path("logo.png")
end
test "stylesheets served with a controller in scope can access the request" do
config.asset_host = Proc.new do |asset, request|
assert_not_nil request
"http://assets-666.example.com"
end
assert_match %r{http://assets-666.example.com/assets/logo-[0-9a-f]+.png},
asset_path("logo.png")
end
test "stylesheets served without a controller in scope cannot access the request" do
remove_instance_variable("@controller")
@config.action_controller.asset_host = Proc.new do |asset, request|
fail "This should not have been called."
end
assert_raises ActionController::RoutingError do
asset_path("logo.png")
end
end
test "asset path with relavtive url root" do
@controller.config.relative_url_root = "/collaboration/hieraki"
assert_equal "/collaboration/hieraki/images/logo.gif",