diff --git a/activerecord/lib/active_record/attribute_methods.rb b/activerecord/lib/active_record/attribute_methods.rb index 3924eec8725e6fbfbb5efe48e15b1ec4109475b1..217fc52dd51d1444d97eb0a60a09866ea6ebb601 100644 --- a/activerecord/lib/active_record/attribute_methods.rb +++ b/activerecord/lib/active_record/attribute_methods.rb @@ -128,6 +128,16 @@ def method_defined_within?(name, klass, sup = klass.superclass) # :nodoc: end end + def find_generated_attribute_method(method_name) # :nodoc: + klass = self + until klass == Base + gen_methods = klass.generated_attribute_methods + return gen_methods.instance_method(method_name) if method_defined_within?(method_name, gen_methods, Object) + klass = klass.superclass + end + nil + end + # Returns +true+ if +attribute+ is an attribute method and table exists, # +false+ otherwise. # @@ -163,7 +173,14 @@ def attribute_names def method_missing(method, *args, &block) # :nodoc: self.class.define_attribute_methods if respond_to_without_attributes?(method) - send(method, *args, &block) + # make sure to invoke the correct attribute method, as we might have gotten here via a `super` + # call in a overwritten attribute method + if attribute_method = self.class.find_generated_attribute_method(method) + # this is probably horribly slow, but should only happen at most once for a given AR class + attribute_method.bind(self).call(*args, &block) + else + send(method, *args, &block) + end else super end diff --git a/activerecord/test/cases/attribute_methods_test.rb b/activerecord/test/cases/attribute_methods_test.rb index 9c66ed354e6e6df963c0e07b21908c424d0dd2ac..6c581a432fd2dd491d226fdbe6cc9afb1be4eb9c 100644 --- a/activerecord/test/cases/attribute_methods_test.rb +++ b/activerecord/test/cases/attribute_methods_test.rb @@ -767,8 +767,7 @@ def test_read_attribute_with_nil_should_not_asplode # that by defining a 'foo' method in the generated methods module for B. # (That module will be inserted between the two, e.g. [B, , A].) def test_inherited_custom_accessors - klass = Class.new(ActiveRecord::Base) do - self.table_name = "topics" + klass = new_topic_like_ar_class do self.abstract_class = true def title; "omg"; end def title=(val); self.author_name = val; end @@ -783,8 +782,40 @@ def title=(val); self.author_name = val; end assert_equal "lol", topic.author_name end + def test_on_the_fly_super_invokable_generated_attribute_methods_via_method_missing + klass = new_topic_like_ar_class do + def title + super + '!' + end + end + + real_topic = topics(:first) + assert_equal real_topic.title + '!', klass.find(real_topic.id).title + end + + def test_on_the_fly_super_invokable_generated_predicate_attribute_methods_via_method_missing + klass = new_topic_like_ar_class do + def title? + !super + end + end + + real_topic = topics(:first) + assert_equal !real_topic.title?, klass.find(real_topic.id).title? + end + private + def new_topic_like_ar_class(&block) + klass = Class.new(ActiveRecord::Base) do + self.table_name = 'topics' + class_eval(&block) + end + + assert_empty klass.generated_attribute_methods.instance_methods(false) + klass + end + def cached_columns Topic.columns.map(&:name) - Topic.serialized_attributes.keys end