【Rails】destoryメソッドの使い方を徹底解説!

Rails

destroyメソッドとは

すでにテーブルに存在するレコードを削除するメソッドです。

destroyメソッドの使い方

destroyメソッドの構文

ruby
1
インスタンス.destroy

使い方の例

ruby
1
2
3
article = Article.find(1)
article.destroy
# articlesテーブルのidが1のレコードを削除

またはdestroyメソッドの引数に削除したいレコードのidを指定しても削除できます。

ruby
1
2
3
4
Article.destroy(削除したいレコードのid)

# articlesテーブルのidが3のレコードを削除
Article.destroy(3)

ただこの記述法はあまり使いません。
この際、引数に指定したidのレコードが存在しない場合は例外が発生します。

ruby
1
2
3
4
1] pry(main)> Artcile.destroy(3)
   (34.8ms)  SET NAMES utf8,  @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'),  @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
  Article Load (1.6ms)  SELECT  `articles`.* FROM `articles` WHERE `articles`.`id` = 3 LIMIT 1
ActiveRecord::RecordNotFound: Couldn't find Article with 'id'=3

実際にdestroyメソッドを使ってみよう

それでは実際のアプリでdestroyメソッドを使ってみます。
削除機能を実装するまでの流れをみていきましょう。

①destroyメソッドはdestroyアクション内で使うのでdestroyアクションが動くルーティングを設定

routes.rb
1
delete  'articles/:id'  => 'articles#destroy'

②ビューファイル内のdestroyアクションを動かすパスを"articles/#{item.id}"と指定
上のようにルーティングを定義したので、リンクもそのように記述します。
「:id」の部分にはarticleのidを入れたいので、「article.id」とします。

ビューファイル
1
<%= link_to "削除", "articles/#{article.id}", method: :delete %>

③destroyアクションを定義
コントローラーのdestroyアクション内でdestroyメソッドを使用します。

コントローラー
1
2
3
4
def destroy
  article = article.find("削除するレコードのid")
  article.destroy
end

findメソッドの引数には削除するレコードのidが入ります。
今回はルーティングで「'articles/:id'」と指定しました。
こう記述するとパスの「:id」に入っている値をコントローラーでは「params[:id]」とすることで取得することができます。

④コントローラーのdestroyアクションの引数に削除するレコードのidを指定

「'articles/:id'」の「:id」の部分はビューファイルでは「"articles/#{article.id}"」と記述しています。
なのでparams[:id]としてあげれば削除したいレコードのidを取得できます。

コントローラー
1
2
3
4
def destroy
  article = Article.find(params[:id])
  article.destroy
end

⑤destroyメソッドが実行され、レコードを削除
これでidを指定できたので、findメソッドで削除したいレコードを取得します。
それを変数articleに代入し、articleにdestroyメソッドを使って指定したidのレコードを削除します。

destroyメソッドはコントローラーのどこに書くか

destroyメソッドは、コントローラーの7つのアクションのうち、destroyの中に記述します。

アクション名 機能
index リソースの一覧を表示させる
show リソースの詳細を表示させる
new 投稿フォームを表示させる
create リソースを追加させる
edit 更新フォームを表示させる
update リソースを更新させる
destroy リソースを削除する

Railsではこの7つのアクションに従ってメソッドを記述することが、可読性を高める上で重要です。使いわけに自信がない場合は、こちらの記事もご参照ください。

resourcesメソッドを徹底解説!

dependentオプション

2つのテーブルで下記のようにアソシエーションを組んでいるとします。

モデル
1
2
3
4
5
# article.rb
belongs_to :user

# user.rb
has_many :articles

このとき、usersテーブルのレコードが削除されたら、それに関連しているarticlesテーブルのレコードも削除しないとエラーが発生してしまいます。

例えばidが5のユーザーが投稿した記事が10個あったとします。
このとき、idが5のユーザーを削除したらidが5のユーザーが投稿した記事が存在しているとおかしなことになりますよね。

そんな時はモデルでdependentオプションを定義します。

user.rb
1
has_many :articles, dependent: :destroy

上のようにdestroyを定義するとユーザーが削除された際、それに関連するarticlesテーブルのレコードも同時にdestroyメソッドが実行され削除されます。

ターミナル
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[1] pry(main)> user = User.find(5)
[2] pry(main)> user.destroy
   (0.2ms)  BEGIN
  Article Load (0.4ms)  SELECT `articles`.* FROM `articles` WHERE `articles`.`user_id` = 5
  SQL (1.1ms)  DELETE FROM `articles` WHERE `articles`.`id` = 74
  SQL (0.3ms)  DELETE FROM `articles` WHERE `articles`.`id` = 90
  SQL (0.2ms)  DELETE FROM `articles` WHERE `articles`.`id` = 92
  SQL (0.2ms)  DELETE FROM `articles` WHERE `articles`.`id` = 93
  SQL (0.2ms)  DELETE FROM `articles` WHERE `articles`.`id` = 97
  SQL (0.2ms)  DELETE FROM `articles` WHERE `articles`.`id` = 98
  SQL (0.4ms)  DELETE FROM `articles` WHERE `articles`.`id` = 99
  SQL (0.3ms)  DELETE FROM `articles` WHERE `articles`.`id` = 100
  SQL (0.5ms)  DELETE FROM `articles` WHERE `articles`.`id` = 101
  SQL (0.2ms)  DELETE FROM `articles` WHERE `articles`.`id` = 103
  SQL (0.2ms)  DELETE FROM `users` WHERE `users`.`id` = 5
   (2.6ms)  COMMIT
=> #<User:0x007feecad4e368
 id: 5
 name: "ピカわか",
 created_at: XXX, XX XXX 20XX XX:XX:XX JST +09:00,
 updated_at: XXX, XX XXX 20XX XX:XX:XX JST +09:00>

このようにusersテーブルのidが5のレコードだけでなく、articlesテーブルのuser_idカラムの値が5のレコードを全て削除してくれます。

deleteメソッドとの違い

destroyメソッドのようにレコードを削除するメソッドにdeleteメソッドがあります。
destroyメソッドはデータを削除するときにActiveRecord、つまりモデルを介すのに対し、deleteメソッドはActiveRecordを介さずにSQLを直接実行してデータを削除します。

モデルを介さないので、deleteメソッドは先ほど紹介した「dependentオプション」は実行されません。

deleteメソッドの場合
1
2
3
4
5
6
7
8
[1] pry(main)> user = User.find(5)
[2] pry(main)> user.delete
  User Destroy (8.8ms)  DELETE FROM `users` WHERE `users`.`id` = 5
 => #<User:0x007feecad4e368
 id: 5
 name: "ピカわか",
 created_at: XXX, XX XXX 20XX XX:XX:XX JST +09:00,
 updated_at: XXX, XX XXX 20XX XX:XX:XX JST +09:00>

destroy!メソッドとの違い

destroyメソッドは削除が成功すると上の例のように削除したインスタンスを、削除が失敗するとfalseを返します。
それに対し、destoy!メソッドは削除が行われなかった時にActiveRecord::RecordNotDestroyed例外を発生させます。

destroyの場合
1
2
3
4
5
6
[1] pry(main)> user = User.find(5)
[2] pry(main)> user.destroy
   (12.4ms)  BEGIN
  Article Exists (41.7ms)  SELECT  1 AS one FROM `articles` WHERE `articles`.`user_id` = 5 LIMIT 1
   (6.5ms)  ROLLBACK
=> false

この場合、条件分岐をしておかないと何らかの原因で削除がされなくても成功した時と同じ挙動をします。

destroy

このように削除ボタンを押しても削除されていないのに、削除された時と同じようにトップページへリダイレクトされてしまいました。
これでは削除されなかった時にすぐに気づくことができません。

destroy!の場合
1
2
3
4
5
6
[1] pry(main)> user = User.find(5)
[2] pry(main)> user.destroy!
   (1.0ms)  BEGIN
  Article Exists (1.3ms)  SELECT  1 AS one FROM `articles` WHERE `articles`.`article_id` = 5 LIMIT 1
   (1.1ms)  ROLLBACK
ActiveRecord::RecordNotDestroyed: Failed to destroy the record

それに対し、destroy!メソッドを使っておくと削除ができなかった場合は下記のようなエラー文が出るので、削除できなかったことがすぐに確認できます。

destroy!

実際はエラーが出てしまうと困るので、destroyメソッドを使い、if文で条件分岐をしておく方が良いでしょう。

ruby
1
2
3
4
5
6
7
8
def destroy
  article = Article.find(params[:id])
  if article.destroy
    redirect_to root_path, notice: "削除が完了しました"
  else
    redirect_to root_path, alert: "削除が失敗しました"
  end
end

条件分岐

ユーザーが投稿したレコードのみ削除できるようにしよう

例えばユーザーが記事を投稿できるアプリがあったとします。
そのアプリに削除機能をつけるのですが、もしユーザーが全ての投稿を削除できてしまったらどうなるでしょう?

そうですね、悪意のあるユーザーが全てのレコードを削除してしまうかもしれません。
それでは困りますよね。
なのでユーザーが投稿したレコードを削除する際、基本的に自分が投稿したレコードしか削除できないよう設定をしておく必要があります。

それには削除ボタン自体を表示させなくすれば良いので、ビューファイルを下記のように記述します。

erb
1
2
3
<% if user_signed_in? && current_user.id == article.user_id %>
  <%= link_to "削除", "articles/#{article.id}", method: :delete %>
<% end %>

上のコードの意味はユーザーがログインしていて、かつログインしているユーザーのidが投稿したレコードのuser_idと同じであれば削除ボタンが表示されます。

削除ボタン

さらにコントローラー側でも下記のように記述しておきましょう。

コントローラー
1
2
3
4
5
6
7
8
9
10
def destroy
  article = Article.find(params[:id])
  if article.user_id == current_user.id
    if article.destroy
      redirect_to root_path, notice: "削除が完了しました"
    else
      redirect_to root_path, alert: "削除が失敗しました"
    end
  end
end

このようにrails側でも念の為に削除したいレコードのidが今ログインしているユーザーのidと等しいという条件をつけておきましょう。

まとめ

・destroyメソッドはレコードを削除するときに使用するメソッドです。
・dependentオプションでdestroyを定義すると関連したレコードも削除することができます。
・deleteメソッドと違い、モデルを介して削除を行います。