Rails 5 supports bi-directional destroy dependency

Abhishek Jain

Abhishek Jain

May 12, 2016

This blog is part of our  Rails 5 series.

In Rails 4.x, it is not possible to have destroy dependency on both sides of a bi-directional association between the two models as it would result in an infinite callback loop causing SystemStackError: stack level too deep.

1
2class User < ActiveRecord::Base
3  has_one :profile, dependent: :destroy
4end
5
6class Profile < ActiveRecord::Base
7  belongs_to :user, dependent: :destroy
8end
9

Calling User#destroy or Profile#destroy would lead to an infinite callback loop.

1
2>> user = User.first
3=> <User id: 4, name: "George">
4
5>> user.profile
6=> <Profile id: 4>
7
8>> user.destroy
9=> DELETE FROM `profiles` WHERE `profiles`.`id` = 4
10   ROLLBACK
11SystemStackError: stack level too deep
12

Rails 5 supports bi-directional destroy dependency without triggering infinite callback loop.

1
2>> user = User.first
3=> <User id: 4, name: "George">
4
5>> user.profile
6=> <Profile id: 4, about: 'Rails developer', works_at: 'ABC'>
7
8>> user.destroy
9=> DELETE FROM "profiles" WHERE "posts"."id" = ?  [["id", 4]]
10   DELETE FROM "users" WHERE "users"."id" = ?  [["id", 4]]
11=> <User id: 4, name: "George">
12

There are many instances like above where we need to destroy an association when it is destroying itself, otherwise it may lead to orphan records.

This feature adds responsibility on developers to ensure adding destroy dependency only when it is required as it can have unintended consequences as shown below.

1
2class User < ApplicationRecord
3  has_many :posts, dependent: :destroy
4end
5
6class Post < ApplicationRecord
7  belongs_to :user, dependent: :destroy
8end
9
10>> user = User.first
11=> <User id: 4, name: "George">
12
13>> user.posts
14=> <ActiveRecord::Associations::CollectionProxy [<Post id: 11, title: 'Ruby', user_id: 4>, #<Post id: 12, title: 'Rails', user_id: 4>]>

As we can see "user" has two posts. Now we will destroy first post.

1>> user.posts.first.destroy
2=> DELETE FROM "posts" WHERE "posts"."id" = ?  [["id", 11]]
3   SELECT "posts".* FROM "posts" WHERE "posts"."user_id" = ?  [["user_id", 4]]
4   DELETE FROM "posts" WHERE "posts"."id" = ?  [["id", 12]]
5   DELETE FROM "users" WHERE "users"."id" = ?  [["id", 4]]
6

As we can see, we wanted to remove post with id "11". However post with id "12" also got deleted. Not only that but user record got deleted too.

In Rails 4.x this would have resulted in SystemStackError: stack level too deep .

So we should use this option very carefully.

If this blog was helpful, check out our full blog archive.

Stay up to date with our blogs.

Subscribe to receive email notifications for new blog posts.