diff --git a/activerecord/CHANGELOG.md b/activerecord/CHANGELOG.md index e5ab6bac58fb908c60f446e06eb1fc181831a902..79247e3028ed571492b63793a42d9d56ca50c364 100644 --- a/activerecord/CHANGELOG.md +++ b/activerecord/CHANGELOG.md @@ -1,5 +1,15 @@ ## Rails 4.0.0 (unreleased) ## +* If inverse_of is true on an association, then when one calls +find()+ on + the association, ActiveRecord will first look through the in-memory objects + in the association for a particular id. Then, it will go to the DB if it + is not found. This is accomplished by calling +find_by_scan+ in + collection associations whenever +options[:inverse_of]+ is not nil. + + Fixes #9470. + + *John Wang* + * `rake db:create` does not change permissions of the MySQL root user. Fixes #8079. diff --git a/activerecord/lib/active_record/associations/collection_association.rb b/activerecord/lib/active_record/associations/collection_association.rb index 906560bd44c82eea1fec8b15001a4e9b72c588dc..73b4a187c89ffd14d0954334bc3a6efcf953d91b 100644 --- a/activerecord/lib/active_record/associations/collection_association.rb +++ b/activerecord/lib/active_record/associations/collection_association.rb @@ -79,7 +79,7 @@ def find(*args) if block_given? load_target.find(*args) { |*block_args| yield(*block_args) } else - if options[:finder_sql] + if options[:finder_sql] || options[:inverse_of] find_by_scan(*args) else scope.find(*args) @@ -567,7 +567,8 @@ def include_in_memory?(record) end end - # If using a custom finder_sql, #find scans the entire collection. + # If using a custom finder_sql or if the :inverse_of option has been + # specified, then #find scans the entire collection. def find_by_scan(*args) expects_array = args.first.kind_of?(Array) ids = args.flatten.compact.map{ |arg| arg.to_i }.uniq diff --git a/activerecord/test/cases/associations/inverse_associations_test.rb b/activerecord/test/cases/associations/inverse_associations_test.rb index 8c9b4fb921bfa06870c763d84d502284915517e0..c8cad84013ec17d195ecd4d27815e36ed38f1722 100644 --- a/activerecord/test/cases/associations/inverse_associations_test.rb +++ b/activerecord/test/cases/associations/inverse_associations_test.rb @@ -278,6 +278,31 @@ def test_parent_instance_should_be_shared_with_first_n_and_last_n_children assert interests[1].man.equal? man end + def test_parent_instance_should_find_child_instance_using_child_instance_id + man = Man.create! + interest = Interest.create! + man.interests = [interest] + + assert interest.equal?(man.interests.first), "The inverse association should use the interest already created and held in memory" + assert interest.equal?(man.interests.find(interest.id)), "The inverse association should use the interest already created and held in memory" + assert man.equal?(man.interests.first.man), "Two inversion should lead back to the same object that was originally held" + assert man.equal?(man.interests.find(interest.id).man), "Two inversions should lead back to the same object that was originally held" + end + + def test_parent_instance_should_find_child_instance_using_child_instance_id_when_created + man = Man.create! + interest = Interest.create!(man: man) + + assert man.equal?(man.interests.first.man), "Two inverses should lead back to the same object that was originally held" + assert man.equal?(man.interests.find(interest.id).man), "Two inversions should lead back to the same object that was originally held" + + assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match before the name is changed" + man.name = "Ben Bitdiddle" + assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match after the parent name is changed" + man.interests.find(interest.id).man.name = "Alyssa P. Hacker" + assert_equal man.name, man.interests.find(interest.id).man.name, "The name of the man should match after the child name is changed" + end + def test_trying_to_use_inverses_that_dont_exist_should_raise_an_error assert_raise(ActiveRecord::InverseOfAssociationNotFoundError) { Man.first.secret_interests } end