【Rails】Active Recordで作ったオブジェクトを削除しよう!(destroy/delete)
Ruby on Railsでデータベースを操作するのに使うActive Record。
今回は、destroy
やdelete
を使って実現するオブジェクトの削除方法紹介します。
今回利用するテーブル情報はこちらです。
基本的なこと
オブジェクトを削除するメソッドはdestroy
とdelete
です。
# deleteはDELETE文が実行される > user.posts.last => #<Post:0x00007fb4d78bb800 id: 7, user_id: nil, title: "5", body: nil, created_at: Thu, 03 Oct 2019 22:47:03 JST +09:00, updated_at: Thu, 03 Oct 2019 22:47:03 JST +09:00, new_user_id: 1> > user.posts.last.delete Post Destroy (2.2ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 7]] => #<Post:0x00007fb4d78bb800 id: 7, user_id: nil, title: "5", body: nil, created_at: Thu, 03 Oct 2019 22:47:03 JST +09:00, updated_at: Thu, 03 Oct 2019 22:47:03 JST +09:00, new_user_id: 1> # ActiveRecordのdestroyが実行される > user.posts.last => #<Post:0x00007fb4d78bb800 id: 7, user_id: nil, title: "5", body: nil, created_at: Thu, 03 Oct 2019 22:47:03 JST +09:00, updated_at: Thu, 03 Oct 2019 22:47:03 JST +09:00, new_user_id: 1> > user.posts.last.destroy (0.3ms) BEGIN (0.2ms) COMMIT => #<Post:0x00007fb4d78bb800 id: 7, user_id: nil, title: "5", body: nil, created_at: Thu, 03 Oct 2019 22:47:03 JST +09:00, updated_at: Thu, 03 Oct 2019 22:47:03 JST +09:00, new_user_id: 1>
destroy
destroy
を実行した場合は関連データを考慮してくれます。
NewUserオブジェクトを削除した時に、紐づくPostオブジェクトも同時に削除してくれます。
# new_user.rb class NewUser < ApplicationRecord has_many :posts, dependent: :destroy end
これを実現しているのがdependent
という設定になるのですが、大きく2種類あります。
destory
: 関連データを削除nullify
: 関連データを削除せずに、nullに更新する
dependent: :destroy
実行結果を見るとわかりますが、先ず始めにPostオブジェクトにDELETE文が走ります。その後にNewUserオブジェクトにDELETE文が実行され関連するデータが全て削除されました。
> Post.all Post Load (0.4ms) SELECT "posts".* FROM "posts" => [#<Post:0x00007fed68ebe4e0 id: 14, user_id: nil, title: "1", body: nil, created_at: Thu, 03 Oct 2019 23:20:16 JST +09:00, updated_at: Thu, 03 Oct 2019 23:20:16 JST +09:00, new_user_id: 4>, #<Post:0x00007fed68ebe350 id: 15, user_id: nil, title: "2", body: nil, created_at: Thu, 03 Oct 2019 23:20:17 JST +09:00, updated_at: Thu, 03 Oct 2019 23:20:17 JST +09:00, new_user_id: 4>, #<Post:0x00007fed68ebe1c0 id: 16, user_id: nil, title: "3", body: nil, created_at: Thu, 03 Oct 2019 23:20:21 JST +09:00, updated_at: Thu, 03 Oct 2019 23:20:21 JST +09:00, new_user_id: 4>] > NewUser.last.destroy NewUser Load (0.5ms) SELECT "new_users".* FROM "new_users" ORDER BY "new_users"."id" DESC LIMIT $1 [["LIMIT", 1]] (0.2ms) BEGIN Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."new_user_id" = $1 [["new_user_id", 4]] Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 14]] Post Destroy (0.2ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 15]] Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 16]] NewUser Destroy (0.4ms) DELETE FROM "new_users" WHERE "new_users"."id" = $1 [["id", 4]] (0.5ms) COMMIT => #<NewUser:0x00007fed4b098ab8 id: 4, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 03 Oct 2019 23:20:02 JST +09:00, updated_at: Thu, 03 Oct 2019 23:20:02 JST +09:00> > Post.all Post Load (0.5ms) SELECT "posts".* FROM "posts" => []
dependent: :nullfy
実行結果を見るとわかりますが、Postオブジェクトは削除されずにnew_user_id
がnullにupdateされています。
そのため、Postデータを明示的に残しておきたい場合はdependent: :nullify
を設定します。
> Post.all Post Load (0.7ms) SELECT "posts".* FROM "posts" => [#<Post:0x00007fbf14ebfb10 id: 8, user_id: nil, title: "1", body: nil, created_at: Thu, 03 Oct 2019 23:14:38 JST +09:00, updated_at: Thu, 03 Oct 2019 23:14:38 JST +09:00, new_user_id: 2>, #<Post:0x00007fbf14ebf958 id: 9, user_id: nil, title: "2", body: nil, created_at: Thu, 03 Oct 2019 23:14:40 JST +09:00, updated_at: Thu, 03 Oct 2019 23:14:40 JST +09:00, new_user_id: 2>, #<Post:0x00007fbf14ebf7c8 id: 10, user_id: nil, title: "3", body: nil, created_at: Thu, 03 Oct 2019 23:14:42 JST +09:00, updated_at: Thu, 03 Oct 2019 23:14:42 JST +09:00, new_user_id: 2>] > NewUser.last.destroy NewUser Load (0.4ms) SELECT "new_users".* FROM "new_users" ORDER BY "new_users"."id" DESC LIMIT $1 [["LIMIT", 1]] (0.2ms) BEGIN Post Update All (0.4ms) UPDATE "posts" SET "new_user_id" = NULL WHERE "posts"."new_user_id" = $1 [["new_user_id", 2]] NewUser Destroy (0.4ms) DELETE FROM "new_users" WHERE "new_users"."id" = $1 [["id", 2]] (1.9ms) COMMIT => #<NewUser:0x00007fbef78b27f8 id: 2, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 03 Oct 2019 23:14:35 JST +09:00, updated_at: Thu, 03 Oct 2019 23:14:35 JST +09:00> > Post.all Post Load (0.4ms) SELECT "posts".* FROM "posts" => [#<Post:0x00007fbef7927b70 id: 8, user_id: nil, title: "1", body: nil, created_at: Thu, 03 Oct 2019 23:14:38 JST +09:00, updated_at: Thu, 03 Oct 2019 23:14:38 JST +09:00, new_user_id: nil>, #<Post:0x00007fbef79279e0 id: 9, user_id: nil, title: "2", body: nil, created_at: Thu, 03 Oct 2019 23:14:40 JST +09:00, updated_at: Thu, 03 Oct 2019 23:14:40 JST +09:00, new_user_id: nil>, #<Post:0x00007fbef7927850 id: 10, user_id: nil, title: "3", body: nil, created_at: Thu, 03 Oct 2019 23:14:42 JST +09:00, updated_at: Thu, 03 Oct 2019 23:14:42 JST +09:00, new_user_id: nil>] [12] pry(main)> NewUser.all NewUser Load (0.4ms) SELECT "new_users".* FROM "new_users" => []
delete
delete
はイメージ的にSQLのDELETE
文を実行するような感じで対象のデータを1件削除します。そのためdestroy
よりも処理は速いです。
が、アプリケーションの処理の中ではほとんど使われないのではないでしょうか?
関連データがあった場合にdelete
しちゃうと整合性が取れず壊れちゃうし、関連データがない場合でもdestroy
で削除することは可能なので。
全件削除する(destroy_all/delete_all)
1件だけじゃなくて全てのデータを削除したい場合はdestroy_all
とdelete_all
があります。
# destroy_allの実行例 > NewUser.all NewUser Load (0.5ms) SELECT "new_users".* FROM "new_users" => [#<NewUser:0x00007fed66dc46b8 id: 5, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 03 Oct 2019 23:25:58 JST +09:00, updated_at: Thu, 03 Oct 2019 23:25:58 JST +09:00>, #<NewUser:0x00007fed66dc4528 id: 6, name: "fuuzo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 03 Oct 2019 23:26:26 JST +09:00, updated_at: Thu, 03 Oct 2019 23:26:26 JST +09:00>] > Post.all Post Load (0.4ms) SELECT "posts".* FROM "posts" => [#<Post:0x00007fed68bac798 id: 17, user_id: nil, title: "1", body: nil, created_at: Thu, 03 Oct 2019 23:26:03 JST +09:00, updated_at: Thu, 03 Oct 2019 23:26:03 JST +09:00, new_user_id: 5>, #<Post:0x00007fed68bac608 id: 18, user_id: nil, title: "2", body: nil, created_at: Thu, 03 Oct 2019 23:26:04 JST +09:00, updated_at: Thu, 03 Oct 2019 23:26:04 JST +09:00, new_user_id: 5>, #<Post:0x00007fed68bac478 id: 19, user_id: nil, title: "3", body: nil, created_at: Thu, 03 Oct 2019 23:26:10 JST +09:00, updated_at: Thu, 03 Oct 2019 23:26:10 JST +09:00, new_user_id: 5>, #<Post:0x00007fed68bac2e8 id: 20, user_id: nil, title: "11", body: nil, created_at: Thu, 03 Oct 2019 23:26:32 JST +09:00, updated_at: Thu, 03 Oct 2019 23:26:32 JST +09:00, new_user_id: 6>, #<Post:0x00007fed68bac158 id: 21, user_id: nil, title: "12", body: nil, created_at: Thu, 03 Oct 2019 23:26:34 JST +09:00, updated_at: Thu, 03 Oct 2019 23:26:34 JST +09:00, new_user_id: 6>, #<Post:0x00007fed68ba7f68 id: 22, user_id: nil, title: "13", body: nil, created_at: Thu, 03 Oct 2019 23:26:35 JST +09:00, updated_at: Thu, 03 Oct 2019 23:26:35 JST +09:00, new_user_id: 6>] > NewUser.destroy_all NewUser Load (0.4ms) SELECT "new_users".* FROM "new_users" (0.2ms) BEGIN Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."new_user_id" = $1 [["new_user_id", 5]] Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 17]] Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 18]] Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 19]] NewUser Destroy (0.4ms) DELETE FROM "new_users" WHERE "new_users"."id" = $1 [["id", 5]] (1.7ms) COMMIT (0.2ms) BEGIN Post Load (0.3ms) SELECT "posts".* FROM "posts" WHERE "posts"."new_user_id" = $1 [["new_user_id", 6]] Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 20]] Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 21]] Post Destroy (0.3ms) DELETE FROM "posts" WHERE "posts"."id" = $1 [["id", 22]] NewUser Destroy (0.4ms) DELETE FROM "new_users" WHERE "new_users"."id" = $1 [["id", 6]] (0.4ms) COMMIT => [#<NewUser:0x00007fed66d75cc0 id: 5, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 03 Oct 2019 23:25:58 JST +09:00, updated_at: Thu, 03 Oct 2019 23:25:58 JST +09:00>, #<NewUser:0x00007fed66d75b30 id: 6, name: "fuuzo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 03 Oct 2019 23:26:26 JST +09:00, updated_at: Thu, 03 Oct 2019 23:26:26 JST +09:00>] > NewUser.all NewUser Load (0.4ms) SELECT "new_users".* FROM "new_users" => [] > Post.all Post Load (0.4ms) SELECT "posts".* FROM "posts" => []
実践的に使う
destroy / destroy!
save
とsave!
と同じように!
を付けると例外を発生させることが可能です。
基本的には全てdestroy!
で問題ないと思います。削除できなかったってことは何か問題が発生したってことなので例外を発生させて気付けるようにしたいので。
条件に応じて削除しないようにする
とはいえ、条件に応じて処理は続行させたい場合もあると思います。
今回の例の場合だと、1件でもPostデータがあればUserデータは削除させないようにするにはbefore_destroy
を使う。
# post.rb class Post < ApplicationRecord belongs_to :new_user before_destroy :do_not_destroy_posted private def do_not_destroy_posted throw :abort end end
> user = NewUser.last NewUser Load (0.7ms) SELECT "new_users".* FROM "new_users" ORDER BY "new_users"."id" DESC LIMIT $1 [["LIMIT", 1]] => #<NewUser:0x00007fab7fe19150 id: 7, name: "opiyo", age: nil, gender: nil, birthday: nil, email: nil, created_at: Thu, 03 Oct 2019 23:43:42 JST +09:00, updated_at: Thu, 03 Oct 2019 23:43:42 JST +09:00> > user.destroy (0.3ms) BEGIN Post Load (0.8ms) SELECT "posts".* FROM "posts" WHERE "posts"."new_user_id" = $1 [["new_user_id", 7]] (0.8ms) ROLLBACK => false
まとめ
削除処理はデータをぶっ壊す可能性がありますので、ちゃんと考えて実装する必要があると思います。
しっかりと関連データまで意識した処理を書いていきたいですね。
- 基本的には
destroy!
dependent
で関連データを扱いを定義する- 条件がある場合は
before_destroy
を使ってバリデーションをかける - なーんも関係なく削除したい場合は
delete
の方が早い