aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activesupport/CHANGELOG2
-rw-r--r--activesupport/lib/active_support/core_ext/file.rb21
-rw-r--r--activesupport/test/core_ext/file_test.rb29
3 files changed, 52 insertions, 0 deletions
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 622ddf1a94..09ea8159a7 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,7 @@
*SVN*
+* Add File.atomic_write, allows you to write large files in an atomic manner, preventing users from seeing half written files. [Koz]
+
* Allow users to provide custom formatters to Logger. [aeden]
* Hash#to_query CGI-escapes its keys. [Jeremy Kemper]
diff --git a/activesupport/lib/active_support/core_ext/file.rb b/activesupport/lib/active_support/core_ext/file.rb
new file mode 100644
index 0000000000..cd43be37e3
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/file.rb
@@ -0,0 +1,21 @@
+require 'tempfile'
+
+# Write to a file atomically. Useful for situations where you don't
+# want other processes or threads to see half-written files.
+#
+# File.atomic_write("important.file") do |file|
+# file.write("hello")
+# end
+#
+# If your temp directory is not on the same filesystem as the file you're
+# trying to write, you can provide a different temporary directory.
+#
+# File.atomic_write("/data/something.imporant", "/data/tmp") do |f|
+# file.write("hello")
+# end
+def File.atomic_write(file_name, temp_dir = Dir.tmpdir)
+ temp_file = Tempfile.new(File.basename(file_name), temp_dir)
+ yield temp_file
+ temp_file.close
+ File.rename(temp_file.path, file_name)
+end \ No newline at end of file
diff --git a/activesupport/test/core_ext/file_test.rb b/activesupport/test/core_ext/file_test.rb
new file mode 100644
index 0000000000..a7a8324125
--- /dev/null
+++ b/activesupport/test/core_ext/file_test.rb
@@ -0,0 +1,29 @@
+require File.dirname(__FILE__) + '/../abstract_unit'
+
+class AtomicWriteTest < Test::Unit::TestCase
+
+ def test_atomic_write_without_errors
+ contents = "Atomic Text"
+ File.atomic_write(file_name) do |file|
+ file.write(contents)
+ assert !File.exists?(file_name)
+ end
+ assert File.exists?(file_name)
+ assert_equal contents, File.read(file_name)
+ ensure
+ File.unlink(file_name)
+ end
+
+ def test_atomic_write_doesnt_write_when_block_raises
+ File.atomic_write(file_name) do |file|
+ file.write("testing")
+ raise "something bad"
+ end
+ rescue
+ assert !File.exists?(file_name)
+ end
+
+ def file_name
+ "atomic.file"
+ end
+end