mirror of
https://github.com/rails/rails.git
synced 2022-11-09 12:12:34 -05:00
Fix AR#method_missing re-dispatching into overwritten attribute methods.
This was happening when a `super` call in an overwritten attribute method was triggering a method_missing fallback, because attribute methods haven't been generated yet. class Topic < ActiveRecord::Base def title # `super` would re-invoke this method if define_attribute_methods # hasn't been called yet resulting in double '!' appending super + '!' end end
This commit is contained in:
parent
414d1eaf6c
commit
e9bf87f08e
2 changed files with 51 additions and 3 deletions
|
@ -128,6 +128,16 @@ module ActiveRecord
|
|||
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 @@ module ActiveRecord
|
|||
def method_missing(method, *args, &block) # :nodoc:
|
||||
if 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
|
||||
|
|
|
@ -767,8 +767,7 @@ class AttributeMethodsTest < ActiveRecord::TestCase
|
|||
# that by defining a 'foo' method in the generated methods module for B.
|
||||
# (That module will be inserted between the two, e.g. [B, <GeneratedAttributes>, 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 @@ class AttributeMethodsTest < ActiveRecord::TestCase
|
|||
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
|
||||
|
|
Loading…
Reference in a new issue