🙄

Rails の through で dependent を設定するとどうなる?

に公開

想定しているのは以下のような関連付けをしている際に through を設定しつつ dependent も設定したらどうなるのか?という検証。

ActiveRecord::Schema.define do
  create_table :users, force: true do |t|
  end

  create_table :user_blogs, force: true do |t|
    t.integer :user_id
    t.integer :blog_id
  end

  create_table :blogs, force: true do |t|
  end
end

class User < ActiveRecord::Base
  has_many :user_blogs
  has_many :blogs, through: :user_blogs, dependent: :destroy
end

class UserBlog < ActiveRecord::Base
  belongs_to :user
  belongs_to :blog
end

class Blog < ActiveRecord::Base
  has_many :user_blogs
end

user = User.new

# through 経由で blog の関連先を扱うことができる
user.blogs.build
user.save!
pp user.reload.blogs
# => [#<Blog:0x0000795dc151d360 id: 1>]

関連付けにもいくつか種類があるんですが一旦中間テーブルを経由して参照する場合にどうなるのか試してみました。

dependent: :destroy を設定するとどうなるのか

has_many :blogs, through: :user_blogs, dependent: :destroy のように設定するとどうなるのか、を試してみました。
期待する挙動としては bloguser_blog まで含めて削除されてほしいんですが…。

class User < ActiveRecord::Base
  has_one :user_blog
  has_one :blog, through: :user_blog, dependent: :destroy
end

class UserBlog < ActiveRecord::Base
  belongs_to :user
  belongs_to :blog
end

class Blog < ActiveRecord::Base
  has_one :user_blog
end

user = User.new
user.blogs.build
user.save!

# 削除前は各レコードが存在している
pp User.first
# => #<User:0x0000782157d18350 id: 1>
pp UserBlog.first
# => #<UserBlog:0x000078219862f980 id: 1, user_id: 1, blog_id: 1>
pp Blog.first
# => #<Blog:0x000078219862dae0 id: 1>

user.destroy!

# 削除後は user と user_blog のみ削除されている
pp User.first
# => nil
pp UserBlog.first
# => nil
pp Blog.first
# => #<Blog:0x0000782157d17bd0 id: 1>

実際には useruser_blog のみ削除されて blog は削除されてないみたいですねー。
blog まで含めて削除するようにしたい場合は UserBlog 側で belongs_to :blog, dependent: :destroy しておけば期待する挙動にはなります。

class User < ActiveRecord::Base
  has_many :user_blogs
  has_many :blogs, through: :user_blogs, dependent: :destroy
end

class UserBlog < ActiveRecord::Base
  belongs_to :user
  belongs_to :blog, dependent: :destroy
end

class Blog < ActiveRecord::Base
  has_many :user_blogs
end

user = User.new
user.blogs.build
user.save!

# 削除前は各レコードが存在している
pp User.first
# => #<User:0x0000782157d18350 id: 1>
pp UserBlog.first
# => #<UserBlog:0x000078219862f980 id: 1, user_id: 1, blog_id: 1>
pp Blog.first
# => #<Blog:0x000078219862dae0 id: 1>

user.destroy!

# これはすべて削除される
pp User.first
# => nil
pp UserBlog.first
# => nil
pp Blog.first
# => nil

まあこれはそれはそう、って感じなんですかねえ。
他の関連付けの場合にどうなるのかも気になる。

おまけ: has_many じゃなくて has_one だとどうなる?

上記は has_many で関連付けしてたんですが has_one の場合にはどうなるのか試してみました。

class User < ActiveRecord::Base
  has_one :user_blog
  has_one :blog, through: :user_blog, dependent: :destroy
end

class UserBlog < ActiveRecord::Base
  belongs_to :user
  belongs_to :blog
end

class Blog < ActiveRecord::Base
  has_one :user_blog
end

user = User.new
user.build_blog
user.save!

# 削除前は各レコードが存在している
pp User.first
# => #<User:0x0000782157d18350 id: 1>
pp UserBlog.first
# => #<UserBlog:0x000078219862f980 id: 1, user_id: 1, blog_id: 1>
pp Blog.first
# => #<Blog:0x000078219862dae0 id: 1>

user.destroy!

# 削除後は user のみ削除されている
pp User.first
# => nil
pp UserBlog.first
# => #<UserBlog:0x00007056dafd6050 id: 1, user_id: 1, blog_id: 1>
pp Blog.first
# => #<Blog:0x00007056dafd5dd0 id: 1>

has_one だと user のみ削除されて user_blogblog は削除されないみたいですね。
has_many と何が違うんだろうか。

GitHubで編集を提案

Discussion