rails/rails — pull request #16632 by fredwu (2014-08-22 08:47:57+00:00)
Title: Fixed the stack level too deep upon saving a record with `inverse_of` set

Fixed the infinite loop (stack level too deep) upon saving a parent record with an associated child record where in the parent the child association is an `inverse_of` the parent.

This is a revised fix based on both @Nerdman4U's #14030 and my #16166. This revision's goals are:

1. To properly fix the issue (which #16166 does but #14030 does not);
2. To make minimal impact to the existing ActiveRecord test suite (which #14030 does but #16166 does not).

The minimal modification to the test suite causes the unpatched code base to fail on this test: `test_should_save_changed_child_objects_if_parent_is_saved`.

This patch backports fine on `4-1-stable`.

The gist provided in #16166 is still relevant and reproduces the problem. The gist below however, also ensures the fix is proper and doesn't break existing behaviour.

```ruby
source 'http://rubygems.org'

gem 'rails', github: 'rails/rails'
gem 'rack', github: 'rack/rack'
gem 'arel', github: 'rails/arel'
gem 'sqlite3'
gem 'pry'
```

```ruby
# uncomment for master
require 'bundler'
Bundler.setup(:default)

# uncomment for releases
# gem 'activerecord', '4.1.5'
# gem 'activerecord', '4.0.9'

require 'pry'
require 'active_record'
require "minitest/autorun"
require 'minitest/pride'
require 'logger'

ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: ':memory:')
ActiveRecord::Base.logger = Logger.new(STDOUT)

ActiveRecord::Schema.define do
create_table :users, force: true do |t|
t.integer :super_power_id
end
create_table :super_powers, force: true do |t|
t.integer :power_id
end
create_table :powers, force: true do |t|
t.string :name
end
end

class User < ActiveRecord::Base
belongs_to :super_power, autosave: true
end

class SuperPower < ActiveRecord::Base
has_one :user, autosave: true
belongs_to :power, autosave: true
end

class Power < ActiveRecord::Base
has_one :super_power, autosave: true
end

class BugTest < MiniTest::Unit::TestCase
def test_autosave
power = Power.new(name: 'power')
power.save!

super_power = SuperPower.new(power: power)
super_power.save!

user = User.new(super_power: super_power)
user.super_power.power.name = 'power2'
user.save!

assert_equal true, user.save!
assert_equal 'power2', user.reload.super_power.power.name
end
end
```

cc @carlosantoniodasilva @rafaelfranca