In Rails 3.0.0-pre, when caching JavaScript and CSS assets, a
mysterious error is generated on systems with a separate partition
for temporary files.
ActionView::Helpers.write_asset_file_contents makes use of
File.atomic_write with no tmp_dir parameter specified. This
defaults to Dir.tmpdir accordingly, however, it appears that if
this directory is on a different volume than the Rails application
then an error will result when apparently trying to create a
hard-link to move the cached asset file:
ActionView::Template::Error (Invalid cross-device link - /tmp//_main.css20100210-24481-ilznwv-0 or public/stylesheets/_main.css)
This was generated from a simple asset-caching request:
stylesheet_link_tag('reset', 'master', 'forms', :cache => '_main')
This would only seem to be an issue on systems where Dir.tmpname
returns a path on a different volume than where Rails.root is
situated. On some Linux installations /tmp is given its own
partition.
A simple patch is to use the default Rails tmp/ directory by
modifying the call in write_asset_file_contents to be:
module ActionView
module Helpers
def write_asset_file_contents(joined_asset_path, asset_paths)
FileUtils.mkdir_p(File.dirname(joined_asset_path))
File.atomic_write(joined_asset_path, File.join(Rails.root, 'tmp')) { |cache| cache.write(join_asset_file_contents(asset_paths)) }
# 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
File.utime(mt, mt, joined_asset_path)
end
end
end
It stands to reason that the environment could be tweaked so
that TMPDIR is assigned accordingly somewhere during the
initialization, but that may alter behavior on a much larger
scale.
The previous implementation in Rails 2.3.5 does not use
atomic_write and does not suffer from this problem:
File.open(joined_asset_path, "w+") { |cache| cache.write(join_asset_file_contents(asset_paths)) }
This has the unfortunate effect of presumably failing if the
Rails application does not have its own tmp/ directory.