diff options
author | Jon Leighton <j@jonathanleighton.com> | 2011-09-11 14:48:40 +0100 |
---|---|---|
committer | Jon Leighton <j@jonathanleighton.com> | 2011-09-13 00:01:58 +0100 |
commit | eecfa84a9065da2b1bd1e02f37e8653a2825c624 (patch) | |
tree | 103ae42eb5c8f6206a5eac7b731307b3348dadb7 | |
parent | 50d395f96ea05da1e02459688e94bff5872c307b (diff) | |
download | rails-eecfa84a9065da2b1bd1e02f37e8653a2825c624.tar.gz rails-eecfa84a9065da2b1bd1e02f37e8653a2825c624.tar.bz2 rails-eecfa84a9065da2b1bd1e02f37e8653a2825c624.zip |
Always generate attribute methods on the base class.
This fixes a situation I encountered where a subclass would cache the
name of a generated attribute method in @_defined_class_methods. Then,
when the superclass has it's attribute methods undefined, the subclass
would always have to dispatch through method_missing, because the
presence of the attribute in @_defined_class_methods would mean that it
is never generated again, even if undefine_attribute_methods is called
on the subclass.
There various other confusing edge cases like this. STI classes share
columns, so let's just keep all the attribute method generation state
isolated to the base class.
-rw-r--r-- | activerecord/lib/active_record/attribute_methods.rb | 23 | ||||
-rw-r--r-- | activerecord/lib/active_record/base.rb | 2 | ||||
-rw-r--r-- | activerecord/test/cases/attribute_methods/read_test.rb | 1 | ||||
-rw-r--r-- | activerecord/test/cases/attribute_methods_test.rb | 16 |
4 files changed, 36 insertions, 6 deletions
diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 1937ac4272..a8cb89fb65 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -11,17 +11,30 @@ module ActiveRecord # accessors, mutators and query methods. def define_attribute_methods return if attribute_methods_generated? - super(column_names) - @attribute_methods_generated = true + + if base_class == self + super(column_names) + @attribute_methods_generated = true + else + base_class.define_attribute_methods + end end def attribute_methods_generated? - @attribute_methods_generated ||= false + if base_class == self + @attribute_methods_generated ||= false + else + base_class.attribute_methods_generated? + end end def undefine_attribute_methods(*args) - super - @attribute_methods_generated = false + if base_class == self + super + @attribute_methods_generated = false + else + base_class.undefine_attribute_methods(*args) + end end # Checks whether the method is defined in the model or any of its subclasses diff --git a/activerecord/lib/active_record/base.rb b/activerecord/lib/active_record/base.rb index 2979ad1cb3..92f80c6eaa 100644 --- a/activerecord/lib/active_record/base.rb +++ b/activerecord/lib/active_record/base.rb @@ -1332,7 +1332,7 @@ MSG # Returns the class descending directly from ActiveRecord::Base or an # abstract class, if any, in the inheritance hierarchy. def class_of_active_record_descendant(klass) - if klass.superclass == Base || klass.superclass.abstract_class? + if klass == Base || klass.superclass == Base || klass.superclass.abstract_class? klass elsif klass.superclass.nil? raise ActiveRecordError, "#{name} doesn't belong in a hierarchy descending from ActiveRecord" diff --git a/activerecord/test/cases/attribute_methods/read_test.rb b/activerecord/test/cases/attribute_methods/read_test.rb index 3641031d12..e03ed33591 100644 --- a/activerecord/test/cases/attribute_methods/read_test.rb +++ b/activerecord/test/cases/attribute_methods/read_test.rb @@ -35,6 +35,7 @@ module ActiveRecord end def self.serialized_attributes; {}; end + def self.base_class; self; end end end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index dbf5a1ba76..9815c4ba87 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -659,6 +659,22 @@ class AttributeMethodsTest < ActiveRecord::TestCase assert_equal %w(preferences), Contact.serialized_attributes.keys end + def test_instance_method_should_be_defined_on_the_base_class + subklass = Class.new(Topic) + + Topic.define_attribute_methods + + instance = subklass.new + instance.id = 5 + assert_equal 5, instance.id + assert subklass.method_defined?(:id), "subklass is missing id method" + + Topic.undefine_attribute_methods + + assert_equal 5, instance.id + assert subklass.method_defined?(:id), "subklass is missing id method" + end + private def cached_columns @cached_columns ||= (time_related_columns_on_topic + serialized_columns_on_topic).map(&:name) |