Skip to content

Commit

Permalink
refactor Reference to a ClassCache object, fix lazy lookup in Middlew…
Browse files Browse the repository at this point in the history
…are so that anonymous classes are supported
  • Loading branch information
tenderlove committed Mar 2, 2011
1 parent 50ed1a2 commit 7b6bfe8
Show file tree
Hide file tree
Showing 7 changed files with 185 additions and 19 deletions.
13 changes: 10 additions & 3 deletions actionpack/lib/action_dispatch/middleware/stack.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,19 @@ class Middleware
attr_reader :args, :block

def initialize(klass_or_name, *args, &block)
@ref = ActiveSupport::Dependencies::Reference.new(klass_or_name)
@klass = nil
@name = klass_or_name

if klass_or_name.respond_to?(:name)
@klass = klass_or_name
@name = @klass.name
end

@args, @block = args, block
end

def klass
@ref.get
@klass ||= ActiveSupport::Inflector.constantize(@name)
end

def ==(middleware)
Expand All @@ -22,7 +29,7 @@ def ==(middleware)
when Class
klass == middleware
else
normalize(@ref.name) == normalize(middleware)
normalize(@name) == normalize(middleware)
end
end

Expand Down
5 changes: 3 additions & 2 deletions actionpack/lib/action_dispatch/routing/route_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ def controller(params, default_controller=true)
private

def controller_reference(controller_param)
controller_name = "#{controller_param.camelize}Controller"

unless controller = @controllers[controller_param]
controller_name = "#{controller_param.camelize}Controller"
controller = @controllers[controller_param] =
ActiveSupport::Dependencies.ref(controller_name)
end
controller.get
controller.get(controller_name)
end

def dispatch(controller, action, env)
Expand Down
50 changes: 50 additions & 0 deletions actionpack/test/dispatch/middleware_stack/middleware_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
require 'abstract_unit'
require 'action_dispatch/middleware/stack'

module ActionDispatch
class MiddlewareStack
class MiddlewareTest < ActiveSupport::TestCase
class Omg; end

{
'concrete' => Omg,
'anonymous' => Class.new
}.each do |name, klass|

define_method("test_#{name}_klass") do
mw = Middleware.new klass
assert_equal klass, mw.klass
end

define_method("test_#{name}_==") do
mw1 = Middleware.new klass
mw2 = Middleware.new klass
assert_equal mw1, mw2
end

end

def test_string_class
mw = Middleware.new Omg.name
assert_equal Omg, mw.klass
end

def test_double_equal_works_with_classes
k = Class.new
mw = Middleware.new k
assert_operator mw, :==, k
assert_operator mw, :!=, Class.new
end

def test_double_equal_works_with_strings
mw = Middleware.new Omg
assert_operator mw, :==, Omg.name
end

def test_double_equal_normalizes_strings
mw = Middleware.new Omg
assert_operator mw, :==, "::#{Omg.name}"
end
end
end
end
6 changes: 6 additions & 0 deletions activesupport/CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
*Rails 3.1.0 (unreleased)*

* ActiveSupport::Dependencies::ClassCache class has been introduced for
holding references to reloadable classes.

* ActiveSupport::Dependencies::Reference has been refactored to take direct
advantage of the new ClassCache.

* Backports Range#cover? as an alias for Range#include? in Ruby 1.8 [Diego Carrion, fxn]

* Added weeks_ago and prev_week to Date/DateTime/Time. [Rob Zolkos, fxn]
Expand Down
43 changes: 32 additions & 11 deletions activesupport/lib/active_support/dependencies.rb
Original file line number Diff line number Diff line change
Expand Up @@ -524,31 +524,52 @@ def remove_unloadable_constants!
explicitly_unloadable_constants.each { |const| remove_constant const }
end

class Reference
@@constants = Hash.new { |h, k| h[k] = Inflector.constantize(k) }
class ClassCache
def initialize
@store = Hash.new { |h, k| h[k] = Inflector.constantize(k) }
end

def empty?
@store.empty?
end

def key?(key)
@store.key?(key)
end

attr_reader :name
def []=(key, value)
return unless key.respond_to?(:name)

def initialize(name)
@name = name.to_s
@@constants[@name] = name if name.respond_to?(:name)
raise(ArgumentError, 'anonymous classes cannot be cached') unless key.name

@store[key.name] = value
end

def [](key)
key = key.name if key.respond_to?(:name)

@store[key]
end
alias :get :[]

def get
@@constants[@name]
def new(name)
self[name] = name
self
end

def self.clear!
@@constants.clear
def clear!
@store.clear
end
end

Reference = ClassCache.new

def ref(name)
references[name] ||= Reference.new(name)
end

def constantize(name)
ref(name).get
ref(name).get(name)
end

# Determine if the given constant has been automatically loaded.
Expand Down
81 changes: 81 additions & 0 deletions activesupport/test/class_cache_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
require 'abstract_unit'
require 'active_support/dependencies'

module ActiveSupport
module Dependencies
class ClassCacheTest < ActiveSupport::TestCase
def setup
@cache = ClassCache.new
end

def test_empty?
assert @cache.empty?
@cache[ClassCacheTest] = ClassCacheTest
assert !@cache.empty?
end

def test_clear!
assert @cache.empty?
@cache[ClassCacheTest] = ClassCacheTest
assert !@cache.empty?
@cache.clear!
assert @cache.empty?
end

def test_set_key
@cache[ClassCacheTest] = ClassCacheTest
assert @cache.key?(ClassCacheTest.name)
end

def test_set_rejects_strings
@cache[ClassCacheTest.name] = ClassCacheTest
assert @cache.empty?
end

def test_get_with_class
@cache[ClassCacheTest] = ClassCacheTest
assert_equal ClassCacheTest, @cache[ClassCacheTest]
end

def test_get_with_name
@cache[ClassCacheTest] = ClassCacheTest
assert_equal ClassCacheTest, @cache[ClassCacheTest.name]
end

def test_get_constantizes
assert @cache.empty?
assert_equal ClassCacheTest, @cache[ClassCacheTest.name]
end

def test_get_is_an_alias
assert_equal @cache[ClassCacheTest], @cache.get(ClassCacheTest.name)
end

def test_new
@cache.new ClassCacheTest
assert @cache.key?(ClassCacheTest.name)
end

def test_new_rejects_strings
@cache.new ClassCacheTest.name
assert !@cache.key?(ClassCacheTest.name)
end

def test_new_returns_self
v = @cache.new ClassCacheTest.name
assert_equal @cache, v
end

def test_anonymous_class_fail
assert_raises(ArgumentError) do
@cache.new Class.new
end

assert_raises(ArgumentError) do
x = Class.new
@cache[x] = x
end
end
end
end
end
6 changes: 3 additions & 3 deletions activesupport/test/dependencies_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -479,13 +479,13 @@ def test_references_should_work
with_loading 'dependencies' do
c = ActiveSupport::Dependencies.ref("ServiceOne")
service_one_first = ServiceOne
assert_equal service_one_first, c.get
assert_equal service_one_first, c.get("ServiceOne")
ActiveSupport::Dependencies.clear
assert ! defined?(ServiceOne)

service_one_second = ServiceOne
assert_not_equal service_one_first, c.get
assert_equal service_one_second, c.get
assert_not_equal service_one_first, c.get("ServiceOne")
assert_equal service_one_second, c.get("ServiceOne")
end
end

Expand Down

0 comments on commit 7b6bfe8

Please sign in to comment.