Merge branch 'master' of git@github.com:rails/rails

This commit is contained in:
Jeremy Kemper 2008-07-17 14:43:08 -07:00
commit 8f9702db5c
5 changed files with 109 additions and 61 deletions

@ -200,10 +200,6 @@ module CompiledTemplates #:nodoc:
end
include CompiledTemplates
# Cache public asset paths
cattr_reader :computed_public_paths
@@computed_public_paths = {}
def self.helper_modules #:nodoc:
helpers = []
Dir.entries(File.expand_path("#{File.dirname(__FILE__)}/helpers")).sort.each do |file|

@ -5,12 +5,12 @@
module ActionView
module Helpers #:nodoc:
# This module provides methods for generating HTML that links views to assets such
# as images, javascripts, stylesheets, and feeds. These methods do not verify
# the assets exist before linking to them.
# as images, javascripts, stylesheets, and feeds. These methods do not verify
# the assets exist before linking to them.
#
# === Using asset hosts
# By default, Rails links to these assets on the current host in the public
# folder, but you can direct Rails to link to assets from a dedicated assets server by
# folder, but you can direct Rails to link to assets from a dedicated assets server by
# setting ActionController::Base.asset_host in your <tt>config/environment.rb</tt>. For example,
# let's say your asset host is <tt>assets.example.com</tt>.
#
@ -22,16 +22,16 @@ module Helpers #:nodoc:
#
# This is useful since browsers typically open at most two connections to a single host,
# which means your assets often wait in single file for their turn to load. You can
# alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com")
# alleviate this by using a <tt>%d</tt> wildcard in <tt>asset_host</tt> (for example, "assets%d.example.com")
# to automatically distribute asset requests among four hosts (e.g., "assets0.example.com" through "assets3.example.com")
# so browsers will open eight connections rather than two.
# so browsers will open eight connections rather than two.
#
# image_tag("rails.png")
# => <img src="http://assets0.example.com/images/rails.png" alt="Rails" />
# stylesheet_link_tag("application")
# => <link href="http://assets3.example.com/stylesheets/application.css" media="screen" rel="stylesheet" type="text/css" />
#
# To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME
# To do this, you can either setup 4 actual hosts, or you can use wildcard DNS to CNAME
# the wildcard to a single asset host. You can read more about setting up your DNS CNAME records from
# your ISP.
#
@ -86,7 +86,7 @@ module Helpers #:nodoc:
# asset far into the future, but still be able to instantly invalidate it by simply updating the file (and hence updating the timestamp,
# which then updates the URL as the timestamp is part of that, which in turn busts the cache).
#
# It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take
# It's the responsibility of the web server you use to set the far-future expiration date on cache assets that you need to take
# advantage of this feature. Here's an example for Apache:
#
# # Asset Expiration
@ -95,16 +95,17 @@ module Helpers #:nodoc:
# ExpiresDefault "access plus 1 year"
# </FilesMatch>
#
# Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must
# Also note that in order for this to work, all your application servers must return the same timestamps. This means that they must
# have their clocks synchronized. If one of them drift out of sync, you'll see different timestamps at random and the cache won't
# work. Which means that the browser will request the same assets over and over again even thought they didn't change. You can use
# something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being
# something like Live HTTP Headers for Firefox to verify that the cache is indeed working (and that the assets are not being
# requested over and over).
module AssetTagHelper
ASSETS_DIR = defined?(Rails.public_path) ? Rails.public_path : "public"
JAVASCRIPTS_DIR = "#{ASSETS_DIR}/javascripts"
STYLESHEETS_DIR = "#{ASSETS_DIR}/stylesheets"
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'].map(&:to_s).freeze unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
# Returns a link tag that browsers and news readers can use to auto-detect
# an RSS or ATOM feed. The +type+ can either be <tt>:rss</tt> (default) or
# <tt>:atom</tt>. Control the link options in url_for format using the
@ -154,10 +155,6 @@ def javascript_path(source)
end
alias_method :path_to_javascript, :javascript_path # aliased to avoid conflicts with a javascript_path named route
JAVASCRIPT_DEFAULT_SOURCES = ['prototype', 'effects', 'dragdrop', 'controls'] unless const_defined?(:JAVASCRIPT_DEFAULT_SOURCES)
@@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
@@stylesheet_expansions = {}
# Returns an html script tag for each of the +sources+ provided. You
# can pass in the filename (.js extension is optional) of javascript files
# that exist in your public/javascripts directory for inclusion into the
@ -193,7 +190,7 @@ def javascript_path(source)
#
# * = The application.js file is only referenced if it exists
#
# Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason
# Though it's not really recommended practice, if you need to extend the default JavaScript set for any reason
# (e.g., you're going to be using a certain .js file in every action), then take a look at the register_javascript_include_default method.
#
# You can also include all javascripts in the javascripts directory using <tt>:all</tt> as the source:
@ -218,7 +215,7 @@ def javascript_path(source)
# You can also cache multiple javascripts into one file, which requires less HTTP connections to download and can better be
# compressed by gzip (leading to faster transfers). Caching will only happen if ActionController::Base.perform_caching
# is set to <tt>true</tt> (which is the case by default for the Rails production environment, but not for the development
# environment).
# environment).
#
# ==== Examples
# javascript_include_tag :all, :cache => true # when ActionController::Base.perform_caching is false =>
@ -259,6 +256,8 @@ def javascript_include_tag(*sources)
end
end
@@javascript_expansions = { :defaults => JAVASCRIPT_DEFAULT_SOURCES.dup }
# Register one or more javascript files to be included when <tt>symbol</tt>
# is passed to <tt>javascript_include_tag</tt>. This method is typically intended
# to be called from plugin initialization to register javascript files
@ -274,6 +273,8 @@ def self.register_javascript_expansion(expansions)
@@javascript_expansions.merge!(expansions)
end
@@stylesheet_expansions = {}
# Register one or more stylesheet files to be included when <tt>symbol</tt>
# is passed to <tt>stylesheet_link_tag</tt>. This method is typically intended
# to be called from plugin initialization to register stylesheet files
@ -439,9 +440,9 @@ def image_path(source)
# <img alt="Icon" height="32" src="/icons/icon.gif" width="32" />
# image_tag("/icons/icon.gif", :class => "menu_icon") # =>
# <img alt="Icon" class="menu_icon" src="/icons/icon.gif" />
# image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
# image_tag("mouse.png", :mouseover => "/images/mouse_over.png") # =>
# <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
# image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
# image_tag("mouse.png", :mouseover => image_path("mouse_over.png")) # =>
# <img src="/images/mouse.png" onmouseover="this.src='/images/mouse_over.png'" onmouseout="this.src='/images/mouse.png'" alt="Mouse" />
def image_tag(source, options = {})
options.symbolize_keys!
@ -454,23 +455,15 @@ def image_tag(source, options = {})
end
if mouseover = options.delete(:mouseover)
options[:onmouseover] = "this.src='#{image_path(mouseover)}'"
options[:onmouseout] = "this.src='#{image_path(options[:src])}'"
options[:onmouseover] = "this.src='#{image_path(mouseover)}'"
options[:onmouseout] = "this.src='#{image_path(options[:src])}'"
end
tag("img", options)
end
private
def file_exist?(path)
@@file_exist_cache ||= {}
if !(@@file_exist_cache[path] ||= File.exist?(path))
@@file_exist_cache[path] = true
false
else
true
end
end
COMPUTED_PUBLIC_PATHS = ActiveSupport::Cache::MemoryStore.new.silence!.threadsafe!
# Add the the extension +ext+ if not present. Return full URLs otherwise untouched.
# Prefix with <tt>/dir/</tt> if lacking a leading +/+. Account for relative URL
@ -490,7 +483,7 @@ def compute_public_path(source, dir, ext = nil, include_host = true)
dir, source, ext, include_host ].join
end
ActionView::Base.computed_public_paths[cache_key] ||=
source = COMPUTED_PUBLIC_PATHS.fetch(cache_key) do
begin
source += ".#{ext}" if ext && File.extname(source).blank? || File.exist?(File.join(ASSETS_DIR, dir, "#{source}.#{ext}"))
@ -507,8 +500,7 @@ def compute_public_path(source, dir, ext = nil, include_host = true)
rewrite_asset_path(source)
end
end
source = ActionView::Base.computed_public_paths[cache_key]
end
if include_host && source !~ %r{^[-a-z]+://}
host = compute_asset_host(source)
@ -594,7 +586,7 @@ def expand_javascript_sources(sources, recursive = false)
expanded_sources = sources.collect do |source|
determine_source(source, @@javascript_expansions)
end.flatten
expanded_sources << "application" if sources.include?(:defaults) && file_exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
expanded_sources << "application" if sources.include?(:defaults) && File.exist?(File.join(JAVASCRIPTS_DIR, "application.js"))
expanded_sources
end
end

@ -39,8 +39,7 @@ def host_with_port() 'localhost' end
@controller.request = @request
ActionView::Helpers::AssetTagHelper::reset_javascript_include_default
ActionView::Base.computed_public_paths.clear
COMPUTED_PUBLIC_PATHS.clear
end
def teardown
@ -161,7 +160,7 @@ def test_javascript_include_tag
ENV["RAILS_ASSET_ID"] = ""
JavascriptIncludeToTag.each { |method, tag| assert_dom_equal(tag, eval(method)) }
ActionView::Base.computed_public_paths.clear
COMPUTED_PUBLIC_PATHS.clear
ENV["RAILS_ASSET_ID"] = "1"
assert_dom_equal(%(<script src="/javascripts/prototype.js?1" type="text/javascript"></script>\n<script src="/javascripts/effects.js?1" type="text/javascript"></script>\n<script src="/javascripts/dragdrop.js?1" type="text/javascript"></script>\n<script src="/javascripts/controls.js?1" type="text/javascript"></script>\n<script src="/javascripts/application.js?1" type="text/javascript"></script>), javascript_include_tag(:defaults))

@ -21,7 +21,7 @@ def self.expand_cache_key(key, namespace = nil)
expanded_cache_key = namespace ? "#{namespace}/" : ""
if ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]
expanded_cache_key << "#{ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]}/"
expanded_cache_key << "#{ENV["RAILS_CACHE_ID"] || ENV["RAILS_APP_VERSION"]}/"
end
expanded_cache_key << case
@ -36,16 +36,15 @@ def self.expand_cache_key(key, namespace = nil)
expanded_cache_key
end
class Store
cattr_accessor :logger
def initialize
def threadsafe!
extend ThreadSafety
end
def threadsafe!
@mutex = Mutex.new
self.class.send :include, ThreadSafety
def silence!
@silence = true
self
end
@ -110,29 +109,24 @@ def decrement(key, amount = 1)
nil
end
end
private
def log(operation, key, options)
logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})" : ""}") if logger && !@logger_off
logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})" : ""}") if logger && !@silence && !@logger_off
end
end
module ThreadSafety #:nodoc:
def read(key, options = nil) #:nodoc:
@mutex.synchronize { super }
def self.extended(object) #:nodoc:
object.instance_variable_set(:@mutex, Mutex.new)
end
def write(key, value, options = nil) #:nodoc:
@mutex.synchronize { super }
end
def delete(key, options = nil) #:nodoc:
@mutex.synchronize { super }
end
def delete_matched(matcher, options = nil) #:nodoc:
@mutex.synchronize { super }
%w(read write delete delete_matched exist? increment decrement).each do |method|
module_eval <<-EOS, __FILE__, __LINE__
def #{method}(*args)
@mutex.synchronize { super }
end
EOS
end
end
end

@ -70,3 +70,70 @@ def test_fetch_with_forced_cache_miss
end
end
end
class ThreadSafetyCacheStoreTest < Test::Unit::TestCase
def setup
@cache = ActiveSupport::Cache.lookup_store(:memory_store).threadsafe!
@cache.write('foo', 'bar')
# No way to have mocha proxy to the original method
@mutex = @cache.instance_variable_get(:@mutex)
@mutex.instance_eval %(
def calls; @calls; end
def synchronize
@calls ||= 0
@calls += 1
yield
end
)
end
def test_read_is_synchronized
assert_equal 'bar', @cache.read('foo')
assert_equal 1, @mutex.calls
end
def test_write_is_synchronized
@cache.write('foo', 'baz')
assert_equal 'baz', @cache.read('foo')
assert_equal 2, @mutex.calls
end
def test_delete_is_synchronized
assert_equal 'bar', @cache.read('foo')
@cache.delete('foo')
assert_equal nil, @cache.read('foo')
assert_equal 3, @mutex.calls
end
def test_delete_matched_is_synchronized
assert_equal 'bar', @cache.read('foo')
@cache.delete_matched(/foo/)
assert_equal nil, @cache.read('foo')
assert_equal 3, @mutex.calls
end
def test_fetch_is_synchronized
assert_equal 'bar', @cache.fetch('foo') { 'baz' }
assert_equal 'fu', @cache.fetch('bar') { 'fu' }
assert_equal 3, @mutex.calls
end
def test_exist_is_synchronized
assert @cache.exist?('foo')
assert !@cache.exist?('bar')
assert_equal 2, @mutex.calls
end
def test_increment_is_synchronized
@cache.write('foo_count', 1)
assert_equal 2, @cache.increment('foo_count')
assert_equal 4, @mutex.calls
end
def test_decrement_is_synchronized
@cache.write('foo_count', 1)
assert_equal 0, @cache.decrement('foo_count')
assert_equal 4, @mutex.calls
end
end