aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
authorPratik Naik <pratiknaik@gmail.com>2008-08-14 16:31:14 +0100
committerPratik Naik <pratiknaik@gmail.com>2008-08-14 16:31:14 +0100
commit2ebe8d275efa53af967b09ad66dab68acc1aed98 (patch)
treeb0b3e42c277b213788b55558aef3579f4e242300 /activesupport
parent73ef94e9675ef6db85f18f1e3c70bf6ddfc1260a (diff)
parent8cb14ee1203c9ed380c4b192e8730757a52d43cb (diff)
downloadrails-2ebe8d275efa53af967b09ad66dab68acc1aed98.tar.gz
rails-2ebe8d275efa53af967b09ad66dab68acc1aed98.tar.bz2
rails-2ebe8d275efa53af967b09ad66dab68acc1aed98.zip
Merge commit 'mainstream/master'
Conflicts: actionpack/lib/action_controller/request.rb actionpack/lib/action_controller/resources.rb
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/lib/active_support/cache.rb18
-rw-r--r--activesupport/lib/active_support/cache/compressed_mem_cache_store.rb8
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb4
-rw-r--r--activesupport/lib/active_support/cache/memory_store.rb24
-rw-r--r--activesupport/lib/active_support/core_ext/file.rb24
-rw-r--r--activesupport/lib/active_support/core_ext/file/atomic.rb46
-rw-r--r--activesupport/lib/active_support/inflector.rb13
-rw-r--r--activesupport/lib/active_support/memoizable.rb58
-rw-r--r--activesupport/test/caching_test.rb70
-rw-r--r--activesupport/test/core_ext/file_test.rb50
-rw-r--r--activesupport/test/memoizable_test.rb35
11 files changed, 223 insertions, 127 deletions
diff --git a/activesupport/lib/active_support/cache.rb b/activesupport/lib/active_support/cache.rb
index 5a064f8bea..95eae3a77e 100644
--- a/activesupport/lib/active_support/cache.rb
+++ b/activesupport/lib/active_support/cache.rb
@@ -39,10 +39,6 @@ module ActiveSupport
class Store
cattr_accessor :logger
- def threadsafe!
- extend ThreadSafety
- end
-
def silence!
@silence = true
self
@@ -115,20 +111,6 @@ module ActiveSupport
logger.debug("Cache #{operation}: #{key}#{options ? " (#{options.inspect})" : ""}") if logger && !@silence && !@logger_off
end
end
-
- module ThreadSafety #:nodoc:
- def self.extended(object) #:nodoc:
- object.instance_variable_set(:@mutex, Mutex.new)
- end
-
- %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
end
diff --git a/activesupport/lib/active_support/cache/compressed_mem_cache_store.rb b/activesupport/lib/active_support/cache/compressed_mem_cache_store.rb
index 9470ac9f66..0bff6cf9ad 100644
--- a/activesupport/lib/active_support/cache/compressed_mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/compressed_mem_cache_store.rb
@@ -1,14 +1,14 @@
module ActiveSupport
module Cache
class CompressedMemCacheStore < MemCacheStore
- def read(name, options = {})
- if value = super(name, options.merge(:raw => true))
+ def read(name, options = nil)
+ if value = super(name, (options || {}).merge(:raw => true))
Marshal.load(ActiveSupport::Gzip.decompress(value))
end
end
- def write(name, value, options = {})
- super(name, ActiveSupport::Gzip.compress(Marshal.dump(value)), options.merge(:raw => true))
+ def write(name, value, options = nil)
+ super(name, ActiveSupport::Gzip.compress(Marshal.dump(value)), (options || {}).merge(:raw => true))
end
end
end
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 5b771b1da0..437679cc05 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -9,13 +9,13 @@ module ActiveSupport
def read(name, options = nil)
super
- File.open(real_file_path(name), 'rb') { |f| f.read } rescue nil
+ File.open(real_file_path(name), 'rb') { |f| Marshal.load(f) } rescue nil
end
def write(name, value, options = nil)
super
ensure_cache_path(File.dirname(real_file_path(name)))
- File.open(real_file_path(name), "wb+") { |f| f.write(value) }
+ File.atomic_write(real_file_path(name), cache_path) { |f| Marshal.dump(value, f) }
rescue => e
RAILS_DEFAULT_LOGGER.error "Couldn't create cache directory: #{name} (#{e.message})" if RAILS_DEFAULT_LOGGER
end
diff --git a/activesupport/lib/active_support/cache/memory_store.rb b/activesupport/lib/active_support/cache/memory_store.rb
index 6f114273e4..a44f877414 100644
--- a/activesupport/lib/active_support/cache/memory_store.rb
+++ b/activesupport/lib/active_support/cache/memory_store.rb
@@ -3,6 +3,13 @@ module ActiveSupport
class MemoryStore < Store
def initialize
@data = {}
+ @mutex = Mutex.new
+ end
+
+ def fetch(key, options = {})
+ @mutex.synchronize do
+ super
+ end
end
def read(name, options = nil)
@@ -16,23 +23,32 @@ module ActiveSupport
end
def delete(name, options = nil)
- super
@data.delete(name)
end
def delete_matched(matcher, options = nil)
- super
@data.delete_if { |k,v| k =~ matcher }
end
def exist?(name,options = nil)
- super
@data.has_key?(name)
end
+ def increment(key, amount = 1)
+ @mutex.synchronize do
+ super
+ end
+ end
+
+ def decrement(key, amount = 1)
+ @mutex.synchronize do
+ super
+ end
+ end
+
def clear
@data.clear
end
end
end
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/core_ext/file.rb b/activesupport/lib/active_support/core_ext/file.rb
index 45d93b220f..e03f8ac44e 100644
--- a/activesupport/lib/active_support/core_ext/file.rb
+++ b/activesupport/lib/active_support/core_ext/file.rb
@@ -1,21 +1,5 @@
-require 'tempfile'
+require 'active_support/core_ext/file/atomic'
-# 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.important", "/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
+class File #:nodoc:
+ extend ActiveSupport::CoreExtensions::File::Atomic
+end
diff --git a/activesupport/lib/active_support/core_ext/file/atomic.rb b/activesupport/lib/active_support/core_ext/file/atomic.rb
new file mode 100644
index 0000000000..4d3cf5423f
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/file/atomic.rb
@@ -0,0 +1,46 @@
+require 'tempfile'
+
+module ActiveSupport #:nodoc:
+ module CoreExtensions #:nodoc:
+ module File #:nodoc:
+ module Atomic
+ # 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.important", "/data/tmp") do |f|
+ # file.write("hello")
+ # end
+ def atomic_write(file_name, temp_dir = Dir.tmpdir)
+ temp_file = Tempfile.new(basename(file_name), temp_dir)
+ yield temp_file
+ temp_file.close
+
+ begin
+ # Get original file permissions
+ old_stat = stat(file_name)
+ rescue Errno::ENOENT
+ # No old permissions, write a temp file to determine the defaults
+ check_name = ".permissions_check.#{Thread.current.object_id}.#{Process.pid}.#{rand(1000000)}"
+ new(check_name, "w")
+ old_stat = stat(check_name)
+ unlink(check_name)
+ end
+
+ # Overwrite original file with temp file
+ rename(temp_file.path, file_name)
+
+ # Set correct permissions on new file
+ chown(old_stat.uid, old_stat.gid, file_name)
+ chmod(old_stat.mode, file_name)
+ end
+ end
+ end
+ end
+end
diff --git a/activesupport/lib/active_support/inflector.rb b/activesupport/lib/active_support/inflector.rb
index 6651569d33..c2738b39fc 100644
--- a/activesupport/lib/active_support/inflector.rb
+++ b/activesupport/lib/active_support/inflector.rb
@@ -291,11 +291,14 @@ module ActiveSupport
# NameError is raised when the name is not in CamelCase or the constant is
# unknown.
def constantize(camel_cased_word)
- unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
- raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
- end
+ names = camel_cased_word.split('::')
+ names.shift if names.empty? || names.first.empty?
- Object.module_eval("::#{$1}", __FILE__, __LINE__)
+ constant = Object
+ names.each do |name|
+ constant = constant.const_defined?(name) ? constant.const_get(name) : constant.const_missing(name)
+ end
+ constant
end
# Turns a number into an ordinal string used to denote the position in an
@@ -326,4 +329,4 @@ require 'active_support/inflections'
require 'active_support/core_ext/string/inflections'
unless String.included_modules.include?(ActiveSupport::CoreExtensions::String::Inflections)
String.send :include, ActiveSupport::CoreExtensions::String::Inflections
-end \ No newline at end of file
+end
diff --git a/activesupport/lib/active_support/memoizable.rb b/activesupport/lib/active_support/memoizable.rb
index 23dd96e4df..6506238ac0 100644
--- a/activesupport/lib/active_support/memoizable.rb
+++ b/activesupport/lib/active_support/memoizable.rb
@@ -10,18 +10,37 @@ module ActiveSupport
end
def freeze_with_memoizable
- methods.each do |method|
- __send__($1) if method.to_s =~ /^_unmemoized_(.*)/
- end unless frozen?
-
+ memoize_all unless frozen?
freeze_without_memoizable
end
+
+ def memoize_all
+ methods.each do |m|
+ if m.to_s =~ /^_unmemoized_(.*)/
+ if method(m).arity == 0
+ __send__($1)
+ else
+ ivar = :"@_memoized_#{$1}"
+ instance_variable_set(ivar, {})
+ end
+ end
+ end
+ end
+
+ def unmemoize_all
+ methods.each do |m|
+ if m.to_s =~ /^_unmemoized_(.*)/
+ ivar = :"@_memoized_#{$1}"
+ instance_variable_get(ivar).clear if instance_variable_defined?(ivar)
+ end
+ end
+ end
end
def memoize(*symbols)
symbols.each do |symbol|
- original_method = "_unmemoized_#{symbol}"
- memoized_ivar = "@_memoized_#{symbol}"
+ original_method = :"_unmemoized_#{symbol}"
+ memoized_ivar = :"@_memoized_#{symbol.to_s.sub(/\?\Z/, '_query').sub(/!\Z/, '_bang')}"
class_eval <<-EOS, __FILE__, __LINE__
include Freezable
@@ -29,14 +48,27 @@ module ActiveSupport
raise "Already memoized #{symbol}" if method_defined?(:#{original_method})
alias #{original_method} #{symbol}
- def #{symbol}(*args)
- #{memoized_ivar} ||= {}
- reload = args.pop if args.last == true || args.last == :reload
+ if instance_method(:#{symbol}).arity == 0
+ def #{symbol}(reload = false)
+ if reload || !defined?(#{memoized_ivar}) || #{memoized_ivar}.empty?
+ #{memoized_ivar} = [#{original_method}.freeze]
+ end
+ #{memoized_ivar}[0]
+ end
+ else
+ def #{symbol}(*args)
+ #{memoized_ivar} ||= {} unless frozen?
+ reload = args.pop if args.last == true || args.last == :reload
- if !reload && #{memoized_ivar} && #{memoized_ivar}.has_key?(args)
- #{memoized_ivar}[args]
- else
- #{memoized_ivar}[args] = #{original_method}(*args).freeze
+ if #{memoized_ivar}
+ if !reload && #{memoized_ivar}.has_key?(args)
+ #{memoized_ivar}[args]
+ elsif #{memoized_ivar}
+ #{memoized_ivar}[args] = #{original_method}(*args).freeze
+ end
+ else
+ #{original_method}(*args)
+ end
end
end
EOS
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index 0af4251962..c5f7fb7fdd 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -71,69 +71,29 @@ uses_mocha 'high-level cache store tests' do
end
end
-class ThreadSafetyCacheStoreTest < Test::Unit::TestCase
+class FileStoreTest < 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
- )
+ @cache = ActiveSupport::Cache.lookup_store(:file_store, Dir.pwd)
end
- def test_read_is_synchronized
+ def test_should_read_and_write_strings
+ @cache.write('foo', 'bar')
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
+ ensure
+ File.delete("foo.cache")
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
+ def test_should_read_and_write_hash
+ @cache.write('foo', {:a => "b"})
+ assert_equal({:a => "b"}, @cache.read('foo'))
+ ensure
+ File.delete("foo.cache")
end
- def test_delete_matched_is_synchronized
- assert_equal 'bar', @cache.read('foo')
- @cache.delete_matched(/foo/)
+ def test_should_read_and_write_nil
+ @cache.write('foo', nil)
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
+ ensure
+ File.delete("foo.cache")
end
end
diff --git a/activesupport/test/core_ext/file_test.rb b/activesupport/test/core_ext/file_test.rb
index 5efe357e9f..eedc6b592b 100644
--- a/activesupport/test/core_ext/file_test.rb
+++ b/activesupport/test/core_ext/file_test.rb
@@ -1,9 +1,8 @@
require 'abstract_unit'
class AtomicWriteTest < Test::Unit::TestCase
-
def test_atomic_write_without_errors
- contents = "Atomic Text"
+ contents = "Atomic Text"
File.atomic_write(file_name, Dir.pwd) do |file|
file.write(contents)
assert !File.exist?(file_name)
@@ -13,7 +12,7 @@ class AtomicWriteTest < Test::Unit::TestCase
ensure
File.unlink(file_name) rescue nil
end
-
+
def test_atomic_write_doesnt_write_when_block_raises
File.atomic_write(file_name) do |file|
file.write("testing")
@@ -22,8 +21,47 @@ class AtomicWriteTest < Test::Unit::TestCase
rescue
assert !File.exist?(file_name)
end
-
- def file_name
- "atomic.file"
+
+ def test_atomic_write_preserves_file_permissions
+ contents = "Atomic Text"
+ File.open(file_name, "w", 0755) do |file|
+ file.write(contents)
+ assert File.exist?(file_name)
+ end
+ assert File.exist?(file_name)
+ assert_equal 0100755, file_mode
+ assert_equal contents, File.read(file_name)
+
+ File.atomic_write(file_name, Dir.pwd) do |file|
+ file.write(contents)
+ assert File.exist?(file_name)
+ end
+ assert File.exist?(file_name)
+ assert_equal 0100755, file_mode
+ assert_equal contents, File.read(file_name)
+ ensure
+ File.unlink(file_name) rescue nil
+ end
+
+ def test_atomic_write_preserves_default_file_permissions
+ contents = "Atomic Text"
+ File.atomic_write(file_name, Dir.pwd) do |file|
+ file.write(contents)
+ assert !File.exist?(file_name)
+ end
+ assert File.exist?(file_name)
+ assert_equal 0100666 ^ File.umask, file_mode
+ assert_equal contents, File.read(file_name)
+ ensure
+ File.unlink(file_name) rescue nil
end
+
+ private
+ def file_name
+ "atomic.file"
+ end
+
+ def file_mode
+ File.stat(file_name).mode
+ end
end
diff --git a/activesupport/test/memoizable_test.rb b/activesupport/test/memoizable_test.rb
index cd84dcda53..135d56f14a 100644
--- a/activesupport/test/memoizable_test.rb
+++ b/activesupport/test/memoizable_test.rb
@@ -16,6 +16,16 @@ uses_mocha 'Memoizable' do
"Josh"
end
+ def name?
+ true
+ end
+ memoize :name?
+
+ def update(name)
+ "Joshua"
+ end
+ memoize :update
+
def age
@age_calls += 1
nil
@@ -88,6 +98,10 @@ uses_mocha 'Memoizable' do
assert_equal 1, @person.name_calls
end
+ def test_memoization_with_punctuation
+ assert_equal true, @person.name?
+ end
+
def test_memoization_with_nil_value
assert_equal nil, @person.age
assert_equal 1, @person.age_calls
@@ -96,6 +110,11 @@ uses_mocha 'Memoizable' do
assert_equal 1, @person.age_calls
end
+ def test_memorized_results_are_immutable
+ assert_equal "Josh", @person.name
+ assert_raise(ActiveSupport::FrozenObjectError) { @person.name.gsub!("Josh", "Gosh") }
+ end
+
def test_reloadable
counter = @calculator.counter
assert_equal 1, @calculator.counter
@@ -105,6 +124,21 @@ uses_mocha 'Memoizable' do
assert_equal 3, @calculator.counter
end
+ def test_unmemoize_all
+ assert_equal 1, @calculator.counter
+
+ assert @calculator.instance_variable_get(:@_memoized_counter).any?
+ @calculator.unmemoize_all
+ assert @calculator.instance_variable_get(:@_memoized_counter).empty?
+
+ assert_equal 2, @calculator.counter
+ end
+
+ def test_memoize_all
+ @calculator.memoize_all
+ assert @calculator.instance_variable_defined?(:@_memoized_counter)
+ end
+
def test_memoization_cache_is_different_for_each_instance
assert_equal 1, @calculator.counter
assert_equal 2, @calculator.counter(:reload)
@@ -114,6 +148,7 @@ uses_mocha 'Memoizable' do
def test_memoized_is_not_affected_by_freeze
@person.freeze
assert_equal "Josh", @person.name
+ assert_equal "Joshua", @person.update("Joshua")
end
def test_memoization_with_args