Active Recordのdependentを使う

August 20, 2022

確認環境

$ bundle exec ruby --version
ruby 2.7.5p203 (2021-11-24 revision f69aeb8314) [x86_64-darwin19]
$ bundle exec rails --version
Rails 6.0.4.6

事前準備

モデル作成

$ rails g model SampleUser name:string
$ rails g model SampleUserItem sample_user_id:integer price:integer name:string

migrate

$ rake db:migrate

リレーションを作成

app/models/sample_user.rb

class SampleUser < ApplicationRecord
  has_many :sample_user_items
end

データの初期化メソッドを用意

$ rails c
...

def init_data
  SampleUser.delete_all
  SampleUserItem.delete_all

  u1 = SampleUser.new(name: 'user1')
  u1.save
  item1 = SampleUserItem.new(sample_user_id: u1.id, name: 'item1', price: 100)
  item1.save
  item2 = SampleUserItem.new(sample_user_id: u1.id, name: 'item2', price: 200)
  item2.save
end

# データを初期化する時にこちらを呼び出してください。
init_data

それぞれのオプションを試す前に、init_data を実行して、データを初期化してください。

挙動確認

:destroy

app/models/sample_user.rb

class SampleUser < ApplicationRecord
  has_many :sample_user_items, dependent: :destroy
end
$ rails c
Loading development environment (Rails 6.0.4.6)
irb(main):001:0> SampleUser.last.destroy
   (1.1ms)  SELECT sqlite_version(*)
  SampleUser Load (0.1ms)  SELECT "sample_users".* FROM "sample_users" ORDER BY "sample_users"."id" DESC LIMIT ?  [["LIMIT", 1]]
   (0.1ms)  begin transaction
  SampleUserItem Load (0.1ms)  SELECT "sample_user_items".* FROM "sample_user_items" WHERE "sample_user_items"."sample_user_id" = ?  [["sample_user_id", 2]]
  SampleUserItem Destroy (0.3ms)  DELETE FROM "sample_user_items" WHERE "sample_user_items"."id" = ?  [["id", 3]]
  SampleUserItem Destroy (0.1ms)  DELETE FROM "sample_user_items" WHERE "sample_user_items"."id" = ?  [["id", 4]]
  SampleUser Destroy (0.2ms)  DELETE FROM "sample_users" WHERE "sample_users"."id" = ?  [["id", 2]]
   (0.8ms)  commit transaction
=> #<SampleUser id: 2, name: "user1", created_at: "2022-08-21 07:23:23", updated_at: "2022-08-21 07:23:23">

ちなみに SampleUser.last.delete では、sample_user_items は削除されません。

:delete_all

belongs_to の場合は :delete を使用します。 今回は has_many なので、 :delete_all を使用します。

app/models/sample_user.rb

class SampleUser < ApplicationRecord
  has_many :sample_user_items, dependent: :delete_all
end
irb(main):017:0> SampleUser.last.destroy
   (0.1ms)  SELECT sqlite_version(*)
  SampleUser Load (0.2ms)  SELECT "sample_users".* FROM "sample_users" ORDER BY "sample_users"."id" DESC LIMIT ?  [["LIMIT", 1]]
   (0.0ms)  begin transaction
  SampleUserItem Destroy (0.2ms)  DELETE FROM "sample_user_items" WHERE "sample_user_items"."sample_user_id" = ?  [["sample_user_id", 3]]
  SampleUser Destroy (0.1ms)  DELETE FROM "sample_users" WHERE "sample_users"."id" = ?  [["id", 3]]
   (0.8ms)  commit transaction
=> #<SampleUser id: 3, name: "user1", created_at: "2022-08-21 07:27:11", updated_at: "2022-08-21 07:27:11">

:restrict_with_exception

app/models/sample_user.rb

class SampleUser < ApplicationRecord
  has_many :sample_user_items, dependent: :restrict_with_exception
end
irb(main):020:0> SampleUser.last.destroy
   (0.1ms)  SELECT sqlite_version(*)
  SampleUser Load (0.2ms)  SELECT "sample_users".* FROM "sample_users" ORDER BY "sample_users"."id" DESC LIMIT ?  [["LIMIT", 1]]
   (0.0ms)  begin transaction
  SampleUserItem Exists? (0.1ms)  SELECT 1 AS one FROM "sample_user_items" WHERE "sample_user_items"."sample_user_id" = ? LIMIT ?  [["sample_user_id", 4], ["LIMIT", 1]]
   (0.0ms)  rollback transaction
Traceback (most recent call last):
        1: from (irb):20
ActiveRecord::DeleteRestrictionError (Cannot delete record because of dependent sample_user_items

:restrict_with_error

app/models/sample_user.rb

class SampleUser < ApplicationRecord
  has_many :sample_user_items, dependent: :restrict_with_error
end
irb(main):034:0> a = SampleUser.last
  SampleUser Load (0.1ms)  SELECT "sample_users".* FROM "sample_users" ORDER BY "sample_users"."id" DESC LIMIT ?  [["LIMIT", 1]]
=> #<SampleUser id: 5, name: "user1", created_at: "2022-08-21 07:33:14", updated_at: "2022-08-21 07:33:14">
irb(main):035:0> a.destroy
   (0.1ms)  begin transaction
  SampleUserItem Exists? (0.2ms)  SELECT 1 AS one FROM "sample_user_items" WHERE "sample_user_items"."sample_user_id" = ? LIMIT ?  [["sample_user_id", 5], ["LIMIT", 1]]
   (0.1ms)  rollback transaction
=> false
irb(main):036:0> a.errors
=> #<ActiveModel::Errors:0x00007f927f6f7750 @base=#<SampleUser id: 5, name: "user1", created_at: "2022-08-21 07:33:14", updated_at: "2022-08-21 07:33:14">, @messages={:base=>["Cannot delete record because dependent sample user items exist"]}, @details={:base=>[{:error=>:"restrict_dependent_destroy.has_many", :record=>"sample user items"}]}>

:nullify

app/models/sample_user.rb

class SampleUser < ApplicationRecord
  has_many :sample_user_items, dependent: :nullify
end
irb(main):039:0> SampleUser.last.destroy
   (0.1ms)  SELECT sqlite_version(*)
  SampleUser Load (0.1ms)  SELECT "sample_users".* FROM "sample_users" ORDER BY "sample_users"."id" DESC LIMIT ?  [["LIMIT", 1]]
   (0.1ms)  begin transaction
  SampleUserItem Update All (0.3ms)  UPDATE "sample_user_items" SET "sample_user_id" = ? WHERE "sample_user_items"."sample_user_id" = ?  [["sample_user_id", nil], ["sample_user_id", 6]]
  SampleUser Destroy (0.1ms)  DELETE FROM "sample_users" WHERE "sample_users"."id" = ?  [["id", 6]]
   (0.8ms)  commit transaction
=> #<SampleUser id: 6, name: "user1", created_at: "2022-08-21 07:39:50", updated_at: "2022-08-21 07:39:50">

SHARE

Profile picture

Written by tamesuu