Skip to content

Commit

Permalink
Fixed STI classes not defining an attribute method if there is a
Browse files Browse the repository at this point in the history
conflicting private method defined on its ancestors.

The problem is that `method_defined_within?(name, klass, superklass)`
only works correclty when `klass` and `superklass` are both `Class`es.

If both `klass` and `superklass` are both `Class`es, they share the
same inheritance chain, so if a method is defined on `klass` but not
`superklass`, this method must be introduced at some point between
`klass` and `superklass`.

This does not work when `superklass` is a `Module`. A `Module`'s
inheritance chain contains just itself. So if a method is defined on
`klass` but not on `superklass`, the method could still be defined
somewhere upstream, e.g. in `Object`.

This fix works by avoiding calling `method_defined_within?` with a
module while still fufilling the requirement (checking that the
method is defined withing `superclass` but not is not a generated
attribute method).

4d8ee28 is likely an attempted partial fix for this problem. This
unrolls that fix and properly check the `superclass` as intended.

Fixes #11569.

Conflicts:
	activerecord/CHANGELOG.md
	activerecord/test/cases/attribute_methods_test.rb
	activerecord/test/schema/schema.rb
  • Loading branch information
chancancode committed Feb 25, 2014
1 parent 5a782db commit d91d946
Show file tree
Hide file tree
Showing 5 changed files with 31 additions and 13 deletions.
7 changes: 7 additions & 0 deletions activerecord/CHANGELOG.md
@@ -1,3 +1,10 @@
* Fixed STI classes not defining an attribute method if there is a
conflicting private method defined on its ancestors.

Fixes #11569.

*Godfrey Chan*

* Fix validation on uniqueness of empty association. * Fix validation on uniqueness of empty association.


*Evgeny Li* *Evgeny Li*
Expand Down
5 changes: 3 additions & 2 deletions activerecord/lib/active_record/attribute_methods.rb
Expand Up @@ -86,8 +86,9 @@ def instance_method_already_implemented?(method_name)
super super
else else
# If B < A and A defines its own attribute method, then we don't want to overwrite that. # If B < A and A defines its own attribute method, then we don't want to overwrite that.
defined = method_defined_within?(method_name, superclass, superclass.generated_attribute_methods) defined = method_defined_within?(method_name, superclass) &&
defined && !ActiveRecord::Base.method_defined?(method_name) || super superclass.instance_method(method_name).owner != superclass.generated_attribute_methods
defined || super
end end
end end


Expand Down
30 changes: 19 additions & 11 deletions activerecord/test/cases/attribute_methods_test.rb
Expand Up @@ -710,19 +710,27 @@ def test_bulk_update_respects_access_control
assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } } assert_raise(ActiveRecord::UnknownAttributeError) { @target.new.attributes = { :title => "Ants in pants" } }
end end


def test_read_attribute_overwrites_private_method_not_considered_implemented def test_global_methods_are_overwritten
# simulate a model with a db column that shares its name an inherited klass = Class.new(ActiveRecord::Base) do
# private method (e.g. Object#system) self.table_name = 'computers'
# end
Object.class_eval do
private assert !klass.instance_method_already_implemented?(:system)
def title; "private!"; end computer = klass.new
assert_nil computer.system
end

def test_global_methods_are_overwritte_when_subclassing
klass = Class.new(ActiveRecord::Base) { self.abstract_class = true }

subklass = Class.new(klass) do
self.table_name = 'computers'
end end
assert !@target.instance_method_already_implemented?(:title)
topic = @target.new
assert_nil topic.title


Object.send(:undef_method, :title) # remove test method from object assert !klass.instance_method_already_implemented?(:system)
assert !subklass.instance_method_already_implemented?(:system)
computer = subklass.new
assert_nil computer.system
end end


def test_instance_method_should_be_defined_on_the_base_class def test_instance_method_should_be_defined_on_the_base_class
Expand Down
1 change: 1 addition & 0 deletions activerecord/test/fixtures/computers.yml
@@ -1,4 +1,5 @@
workstation: workstation:
id: 1 id: 1
system: 'Linux'
developer: 1 developer: 1
extendedWarranty: 1 extendedWarranty: 1
1 change: 1 addition & 0 deletions activerecord/test/schema/schema.rb
Expand Up @@ -197,6 +197,7 @@ def create_table(*args, &block)
create_table :computers, :force => true do |t| create_table :computers, :force => true do |t|
t.integer :developer, :null => false t.integer :developer, :null => false
t.integer :extendedWarranty, :null => false t.integer :extendedWarranty, :null => false
t.string :system
end end


create_table :contracts, :force => true do |t| create_table :contracts, :force => true do |t|
Expand Down

0 comments on commit d91d946

Please sign in to comment.