aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorXavier Noria <fxn@hashref.com>2012-12-01 04:38:36 -0800
committerXavier Noria <fxn@hashref.com>2012-12-01 04:38:36 -0800
commit60edeceda3317043a43e3b9aa9087dec4b6a81fb (patch)
tree9a3f7c9fab2bfce6c1ceb136e72be133f58fc8df
parent005d910624bbfa724b638426a000c8074d4201a2 (diff)
parent9ee0ffb3602720fceaecc2fbae1c932341482e31 (diff)
downloadrails-60edeceda3317043a43e3b9aa9087dec4b6a81fb.tar.gz
rails-60edeceda3317043a43e3b9aa9087dec4b6a81fb.tar.bz2
rails-60edeceda3317043a43e3b9aa9087dec4b6a81fb.zip
Merge pull request #8246 from urielka/uriel-fixed-8167
Fix #8167 - adding autoloading support for caching
-rw-r--r--activesupport/CHANGELOG.md5
-rw-r--r--activesupport/lib/active_support/cache/file_store.rb1
-rw-r--r--activesupport/lib/active_support/cache/mem_cache_store.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/marshal.rb21
-rw-r--r--activesupport/test/caching_test.rb42
-rw-r--r--activesupport/test/core_ext/marshal_test.rb124
-rw-r--r--activesupport/test/dependecies_test_helpers.rb27
-rw-r--r--activesupport/test/dependencies_test.rb21
-rw-r--r--guides/source/active_support_core_extensions.md21
9 files changed, 244 insertions, 19 deletions
diff --git a/activesupport/CHANGELOG.md b/activesupport/CHANGELOG.md
index 12d81436ff..450669968b 100644
--- a/activesupport/CHANGELOG.md
+++ b/activesupport/CHANGELOG.md
@@ -1,5 +1,10 @@
## Rails 4.0.0 (unreleased) ##
+* Patched Marshal#load to work with constant autoloading.
+ Fixes autoloading with cache stores that relay on Marshal(MemCacheStore and FileStore). [fixes #8167]
+
+ *Uriel Katz*
+
* Make `Time.zone.parse` to work with JavaScript format date strings. *Andrew White*
* Add `DateTime#seconds_until_end_of_day` and `Time#seconds_until_end_of_day`
diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb
index 2c1ad60d44..8e265ad863 100644
--- a/activesupport/lib/active_support/cache/file_store.rb
+++ b/activesupport/lib/active_support/cache/file_store.rb
@@ -1,3 +1,4 @@
+require 'active_support/core_ext/marshal'
require 'active_support/core_ext/file/atomic'
require 'active_support/core_ext/string/conversions'
require 'uri/common'
diff --git a/activesupport/lib/active_support/cache/mem_cache_store.rb b/activesupport/lib/active_support/cache/mem_cache_store.rb
index 17450fe4d0..712db2c75a 100644
--- a/activesupport/lib/active_support/cache/mem_cache_store.rb
+++ b/activesupport/lib/active_support/cache/mem_cache_store.rb
@@ -6,6 +6,7 @@ rescue LoadError => e
end
require 'digest/md5'
+require 'active_support/core_ext/marshal'
module ActiveSupport
module Cache
diff --git a/activesupport/lib/active_support/core_ext/marshal.rb b/activesupport/lib/active_support/core_ext/marshal.rb
new file mode 100644
index 0000000000..fec3051c0c
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/marshal.rb
@@ -0,0 +1,21 @@
+module Marshal
+ class << self
+ def load_with_autoloading(source)
+ begin
+ load_without_autoloading(source)
+ rescue ArgumentError, NameError => exc
+ if exc.message.match(%r|undefined class/module (.+)|)
+ # try loading the class/module
+ $1.constantize
+ # if it is a IO we need to go back to read the object
+ source.rewind if source.respond_to?(:rewind)
+ retry
+ else
+ raise exc
+ end
+ end
+ end
+
+ alias_method_chain :load, :autoloading
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb
index ed903746c8..a2e2c51283 100644
--- a/activesupport/test/caching_test.rb
+++ b/activesupport/test/caching_test.rb
@@ -1,6 +1,7 @@
require 'logger'
require 'abstract_unit'
require 'active_support/cache'
+require 'dependecies_test_helpers'
class CacheKeyTest < ActiveSupport::TestCase
def test_entry_legacy_optional_ivars
@@ -562,6 +563,45 @@ module LocalCacheBehavior
end
end
+module AutoloadingCacheBehavior
+ include DependeciesTestHelpers
+ def test_simple_autoloading
+ with_autoloading_fixtures do
+ @cache.write('foo', E.new)
+ end
+
+ remove_constants(:E)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ assert_kind_of E, @cache.read('foo')
+ end
+
+ remove_constants(:E)
+ ActiveSupport::Dependencies.clear
+ end
+
+ def test_two_classes_autoloading
+ with_autoloading_fixtures do
+ @cache.write('foo', [E.new, ClassFolder.new])
+ end
+
+ remove_constants(:E, :ClassFolder)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ loaded = @cache.read('foo')
+ assert_kind_of Array, loaded
+ assert_equal 2, loaded.size
+ assert_kind_of E, loaded[0]
+ assert_kind_of ClassFolder, loaded[1]
+ end
+
+ remove_constants(:E, :ClassFolder)
+ ActiveSupport::Dependencies.clear
+ end
+end
+
class FileStoreTest < ActiveSupport::TestCase
def setup
Dir.mkdir(cache_dir) unless File.exist?(cache_dir)
@@ -585,6 +625,7 @@ class FileStoreTest < ActiveSupport::TestCase
include LocalCacheBehavior
include CacheDeleteMatchedBehavior
include CacheIncrementDecrementBehavior
+ include AutoloadingCacheBehavior
def test_clear
filepath = File.join(cache_dir, ".gitkeep")
@@ -745,6 +786,7 @@ class MemCacheStoreTest < ActiveSupport::TestCase
include LocalCacheBehavior
include CacheIncrementDecrementBehavior
include EncodedKeyCacheBehavior
+ include AutoloadingCacheBehavior
def test_raw_values
cache = ActiveSupport::Cache.lookup_store(:mem_cache_store, :raw => true)
diff --git a/activesupport/test/core_ext/marshal_test.rb b/activesupport/test/core_ext/marshal_test.rb
new file mode 100644
index 0000000000..ac79b15fa8
--- /dev/null
+++ b/activesupport/test/core_ext/marshal_test.rb
@@ -0,0 +1,124 @@
+require 'abstract_unit'
+require 'active_support/core_ext/marshal'
+require 'dependecies_test_helpers'
+
+class MarshalTest < ActiveSupport::TestCase
+ include ActiveSupport::Testing::Isolation
+ include DependeciesTestHelpers
+
+ def teardown
+ ActiveSupport::Dependencies.clear
+ remove_constants(:E, :ClassFolder)
+ end
+
+ test "that Marshal#load still works" do
+ sanity_data = ["test", [1, 2, 3], {a: [1, 2, 3]}, ActiveSupport::TestCase]
+ sanity_data.each do |obj|
+ dumped = Marshal.dump(obj)
+ assert_equal Marshal.load_without_autoloading(dumped), Marshal.load(dumped)
+ end
+ end
+
+ test "that a missing class is autoloaded from string" do
+ dumped = nil
+ with_autoloading_fixtures do
+ dumped = Marshal.dump(E.new)
+ end
+
+ remove_constants(:E)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ assert_kind_of E, Marshal.load(dumped)
+ end
+ end
+
+ test "that classes in sub modules work" do
+ dumped = nil
+ with_autoloading_fixtures do
+ dumped = Marshal.dump(ClassFolder::ClassFolderSubclass.new)
+ end
+
+ remove_constants(:ClassFolder)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ assert_kind_of ClassFolder::ClassFolderSubclass, Marshal.load(dumped)
+ end
+ end
+
+ test "that more than one missing class is autoloaded" do
+ dumped = nil
+ with_autoloading_fixtures do
+ dumped = Marshal.dump([E.new, ClassFolder.new])
+ end
+
+ remove_constants(:E, :ClassFolder)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ loaded = Marshal.load(dumped)
+ assert_equal 2, loaded.size
+ assert_kind_of E, loaded[0]
+ assert_kind_of ClassFolder, loaded[1]
+ end
+ end
+
+ test "that a real missing class is causing an exception" do
+ dumped = nil
+ with_autoloading_fixtures do
+ dumped = Marshal.dump(E.new)
+ end
+
+ remove_constants(:E)
+ ActiveSupport::Dependencies.clear
+
+ assert_raise(NameError) do
+ Marshal.load(dumped)
+ end
+ end
+
+ test "when first class is autoloaded and second not" do
+ dumped = nil
+ class SomeClass
+ end
+
+ with_autoloading_fixtures do
+ dumped = Marshal.dump([E.new, SomeClass.new])
+ end
+
+ remove_constants(:E)
+ self.class.send(:remove_const, :SomeClass)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ assert_raise(NameError) do
+ Marshal.load(dumped)
+ end
+
+ assert_nothing_raised("E failed to load while we expect only SomeClass to fail loading") do
+ E.new
+ end
+
+ assert_raise(NameError, "We expected SomeClass to not be loaded but it is!") do
+ SomeClass.new
+ end
+ end
+ end
+
+ test "loading classes from files trigger autoloading" do
+ Tempfile.open("object_serializer_test") do |f|
+ with_autoloading_fixtures do
+ Marshal.dump(E.new, f)
+ end
+
+ f.rewind
+ remove_constants(:E)
+ ActiveSupport::Dependencies.clear
+
+ with_autoloading_fixtures do
+ assert_kind_of E, Marshal.load(f)
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/dependecies_test_helpers.rb b/activesupport/test/dependecies_test_helpers.rb
new file mode 100644
index 0000000000..4b46d32fb4
--- /dev/null
+++ b/activesupport/test/dependecies_test_helpers.rb
@@ -0,0 +1,27 @@
+module DependeciesTestHelpers
+ def with_loading(*from)
+ old_mechanism, ActiveSupport::Dependencies.mechanism = ActiveSupport::Dependencies.mechanism, :load
+ this_dir = File.dirname(__FILE__)
+ parent_dir = File.dirname(this_dir)
+ path_copy = $LOAD_PATH.dup
+ $LOAD_PATH.unshift(parent_dir) unless $LOAD_PATH.include?(parent_dir)
+ prior_autoload_paths = ActiveSupport::Dependencies.autoload_paths
+ ActiveSupport::Dependencies.autoload_paths = from.collect { |f| "#{this_dir}/#{f}" }
+ yield
+ ensure
+ $LOAD_PATH.replace(path_copy)
+ ActiveSupport::Dependencies.autoload_paths = prior_autoload_paths
+ ActiveSupport::Dependencies.mechanism = old_mechanism
+ ActiveSupport::Dependencies.explicitly_unloadable_constants = []
+ end
+
+ def with_autoloading_fixtures(&block)
+ with_loading 'autoloading_fixtures', &block
+ end
+
+ def remove_constants(*constants)
+ constants.each do |constant|
+ Object.send(:remove_const, constant) if Object.const_defined?(constant)
+ end
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/dependencies_test.rb b/activesupport/test/dependencies_test.rb
index 7fea72dab5..952c82f9d8 100644
--- a/activesupport/test/dependencies_test.rb
+++ b/activesupport/test/dependencies_test.rb
@@ -1,6 +1,7 @@
require 'abstract_unit'
require 'pp'
require 'active_support/dependencies'
+require 'dependecies_test_helpers'
module ModuleWithMissing
mattr_accessor :missing_count
@@ -19,25 +20,7 @@ class DependenciesTest < ActiveSupport::TestCase
ActiveSupport::Dependencies.clear
end
- def with_loading(*from)
- old_mechanism, ActiveSupport::Dependencies.mechanism = ActiveSupport::Dependencies.mechanism, :load
- this_dir = File.dirname(__FILE__)
- parent_dir = File.dirname(this_dir)
- path_copy = $LOAD_PATH.dup
- $LOAD_PATH.unshift(parent_dir) unless $LOAD_PATH.include?(parent_dir)
- prior_autoload_paths = ActiveSupport::Dependencies.autoload_paths
- ActiveSupport::Dependencies.autoload_paths = from.collect { |f| "#{this_dir}/#{f}" }
- yield
- ensure
- $LOAD_PATH.replace(path_copy)
- ActiveSupport::Dependencies.autoload_paths = prior_autoload_paths
- ActiveSupport::Dependencies.mechanism = old_mechanism
- ActiveSupport::Dependencies.explicitly_unloadable_constants = []
- end
-
- def with_autoloading_fixtures(&block)
- with_loading 'autoloading_fixtures', &block
- end
+ include DependeciesTestHelpers
def test_depend_on_path
skip "LoadError#path does not exist" if RUBY_VERSION < '2.0.0'
diff --git a/guides/source/active_support_core_extensions.md b/guides/source/active_support_core_extensions.md
index e6f2db2a2d..9fca3d585b 100644
--- a/guides/source/active_support_core_extensions.md
+++ b/guides/source/active_support_core_extensions.md
@@ -3720,6 +3720,27 @@ The auxiliary file is written in a standard directory for temporary files, but y
NOTE: Defined in `active_support/core_ext/file/atomic.rb`.
+Extensions to `Marshal`
+--------------------
+
+### `load`
+
+Unpatched Marshal#load doesn't call constant_missing so in order to support ActiveSupport constant autoloading load is patched using alias_method_chain.
+
+The method accepts the same arguments as unpatched Marshal#load and the result of calling it will be the same.
+
+For example, ActiveSupport uses this method to read from cache(in FileStore):
+
+```ruby
+File.open(file_name) { |f| Marshal.load(f) }
+```
+
+If Marshal#load didn't support constant autoloading then various caching stores wouldn't too.
+
+WARNING. If a IO (e.g. a file) is used as source it needs to respond to rewind (which a normal file does) because if an exception is raised calling the original Marshal#load the file will be exhausted.
+
+NOTE: Defined in `active_support/core_ext/marshal.rb`.
+
Extensions to `Logger`
----------------------