aboutsummaryrefslogtreecommitdiffstats
path: root/activesupport
diff options
context:
space:
mode:
Diffstat (limited to 'activesupport')
-rw-r--r--activesupport/CHANGELOG2
-rw-r--r--activesupport/lib/active_support/core_ext/class.rb1
-rw-r--r--activesupport/lib/active_support/core_ext/class/delegating_attributes.rb40
-rw-r--r--activesupport/test/core_ext/class/delegating_attributes_test.rb105
4 files changed, 148 insertions, 0 deletions
diff --git a/activesupport/CHANGELOG b/activesupport/CHANGELOG
index 80f6c76401..4b22b6e7b2 100644
--- a/activesupport/CHANGELOG
+++ b/activesupport/CHANGELOG
@@ -1,5 +1,7 @@
*SVN*
+* Add new superclass_delegating_accessors. Similar to class inheritable attributes but with subtly different semantics. [Koz, tarmo]
+
* Change JSON to encode %w(< > &) as 4 digit hex codes to be in compliance with the JSON spec. Closes #9975 [josh, chuyeow, tpope]
* Fix JSON encoding/decoding bugs dealing with /'s. Closes #9990 [Rick, theamazingrando]
diff --git a/activesupport/lib/active_support/core_ext/class.rb b/activesupport/lib/active_support/core_ext/class.rb
index 83eb8135f4..44ad6c8c08 100644
--- a/activesupport/lib/active_support/core_ext/class.rb
+++ b/activesupport/lib/active_support/core_ext/class.rb
@@ -1,3 +1,4 @@
require 'active_support/core_ext/class/attribute_accessors'
require 'active_support/core_ext/class/inheritable_attributes'
require 'active_support/core_ext/class/removal'
+require 'active_support/core_ext/class/delegating_attributes'
diff --git a/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
new file mode 100644
index 0000000000..f5f0ef8779
--- /dev/null
+++ b/activesupport/lib/active_support/core_ext/class/delegating_attributes.rb
@@ -0,0 +1,40 @@
+# These class attributes behave something like the class
+# inheritable accessors. But instead of copying the hash over at
+# the time the subclass is first defined, the accessors simply
+# delegate to their superclass unless they have been given a
+# specific value. This stops the strange situation where values
+# set after class definition don't get applied to subclasses.
+class Class
+ def superclass_delegating_reader(*names)
+ class_name_to_stop_searching_on = self.superclass.name.blank? ? "Object" : self.superclass.name
+ names.each do |name|
+ class_eval <<-EOS
+ def self.#{name}
+ if defined?(@#{name})
+ @#{name}
+ elsif superclass < #{class_name_to_stop_searching_on} && superclass.respond_to?(:#{name})
+ superclass.#{name}
+ end
+ end
+ def #{name}
+ self.class.#{name}
+ end
+ EOS
+ end
+ end
+
+ def superclass_delegating_writer(*names)
+ names.each do |name|
+ class_eval <<-EOS
+ def self.#{name}=(value)
+ @#{name} = value
+ end
+ EOS
+ end
+ end
+
+ def superclass_delegating_accessor(*names)
+ superclass_delegating_reader(*names)
+ superclass_delegating_writer(*names)
+ end
+end \ No newline at end of file
diff --git a/activesupport/test/core_ext/class/delegating_attributes_test.rb b/activesupport/test/core_ext/class/delegating_attributes_test.rb
new file mode 100644
index 0000000000..59c0a6f63c
--- /dev/null
+++ b/activesupport/test/core_ext/class/delegating_attributes_test.rb
@@ -0,0 +1,105 @@
+require File.dirname(__FILE__) + '/../../abstract_unit'
+
+module DelegatingFixtures
+ class Parent
+ end
+
+ class Child < Parent
+ superclass_delegating_accessor :some_attribute
+ end
+
+ class Mokopuna < Child
+ end
+end
+
+class DelegatingAttributesTest < Test::Unit::TestCase
+ include DelegatingFixtures
+ attr_reader :single_class
+
+ def setup
+ @single_class = Class.new(Object)
+ end
+
+ def test_simple_reader_declaration
+ single_class.superclass_delegating_reader :only_reader
+ # The class and instance should have an accessor, but there
+ # should be no mutator
+ assert single_class.respond_to?(:only_reader)
+ assert single_class.public_instance_methods.include?("only_reader")
+ assert !single_class.respond_to?(:only_reader=)
+ end
+
+ def test_simple_writer_declaration
+ single_class.superclass_delegating_writer :only_writer
+ # The class should have a mutator, the instances shouldn't
+ # neither should have an accessor
+ assert single_class.respond_to?(:only_writer=)
+ assert !single_class.public_instance_methods.include?("only_writer=")
+ assert !single_class.public_instance_methods.include?("only_writer")
+ assert !single_class.respond_to?(:only_writer)
+ end
+
+ def test_simple_accessor_declaration
+ single_class.superclass_delegating_accessor :both
+ # Class should have accessor and mutator
+ # the instance should have an accessor only
+ assert single_class.respond_to?(:both)
+ assert single_class.respond_to?(:both=)
+ assert single_class.public_instance_methods.include?("both")
+ assert !single_class.public_instance_methods.include?("both=")
+ end
+
+ def test_working_with_simple_attributes
+ single_class.superclass_delegating_accessor :both
+ single_class.both= "HMMM"
+ assert_equal "HMMM", single_class.both
+ assert_equal "HMMM", single_class.new.both
+ end
+
+ def test_working_with_accessors
+ single_class.superclass_delegating_reader :only_reader
+ single_class.instance_variable_set("@only_reader", "reading only")
+ assert_equal "reading only", single_class.only_reader
+ assert_equal "reading only", single_class.new.only_reader
+ end
+
+ def test_working_with_simple_mutators
+ single_class.superclass_delegating_writer :only_writer
+ single_class.only_writer="written"
+ assert_equal "written", single_class.instance_variable_get("@only_writer")
+ end
+
+ def test_child_class_delegates_to_parent_but_can_be_overridden
+ parent = Class.new
+ parent.superclass_delegating_accessor :both
+ child = Class.new(parent)
+ parent.both= "1"
+ assert_equal "1", child.both
+
+ child.both="2"
+ assert_equal "1", parent.both
+ assert_equal "2", child.both
+
+ parent.both="3"
+ assert_equal "3", parent.both
+ assert_equal "2", child.both
+ end
+
+ def test_delegation_stops_at_the_right_level
+ assert_nil Mokopuna.some_attribute
+ assert_nil Child.some_attribute
+ Child.some_attribute="1"
+ assert_equal "1", Mokopuna.some_attribute
+ ensure
+ Child.some_attribute=nil
+ end
+
+ def test_delegation_stops_for_nil
+ Mokopuna.some_attribute = nil
+ Child.some_attribute="1"
+
+ assert_equal "1", Child.some_attribute
+ assert_nil Mokopuna.some_attribute
+ end
+
+end \ No newline at end of file