すでにメンバーの場合は

無料会員登録

GitHubアカウントで登録 Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

登録がまだの方はこちらから

Pikawakaにログイン

GitHubアカウントでログイン Pikawakaが許可なくTwitterやFacebookに投稿することはありません。

Rails

【Rails】 destroyメソッドの使い方とは?

ぴっかちゃん
ぴっかちゃん

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

destroyメソッドの構文
1
インスタンス.destroy
destroyメソッドの使用例
1
2
3
article = Article.find(1)
article.destroy
# articlesテーブルのidが1のレコードを削除

destroyメソッドの使い方

この章では、destroyメソッドの使い方について解説します。

destroyメソッドの引数

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

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

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

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

コンソール
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メソッドを使ってみます。
削除機能を実装するまでの流れをみていきましょう。

  1. ルーティングを設定
  2. パスの指定
  3. destroyアクションを定義
  4. 削除するレコードのidの指定
  5. レコードの削除

1. ルーティングを設定

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

routes.rb | ルーティング設定
1
delete  'articles/:id'  => 'articles#destroy'

2.パスの指定

ビューファイル内のdestroyアクションを動かすパスを"articles/#{item.id}"と指定します。

ルーティングを定義したので、リンクもそのように記述します。
:idの部分にはarticleのidを入れたいので、article.idとします。

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

3.destroyアクションを定義

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

コントローラー | destroyアクションを定義
1
2
3
4
def destroy
  article = article.find("削除するレコードのid")
  article.destroy
end

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

4.削除するレコードのidの指定

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

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

コントローラー | 削除するレコードのidの指定
1
2
3
4
def destroy
  article = Article.find(params[:id])
  article.destroy
end

5.レコードの削除

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文で条件分岐をしておく方が良いでしょう。

コントローラー
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

条件分岐

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

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

悪意のあるユーザーが全てのレコードを削除してしまうかもしれません。それでは困りますよね。

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

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

ビューファイル
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と等しいという条件をつけておきましょう。Railsについてもっと学びたい!という方は、複数人での開発体制やテストコードの書き方など実際の開発現場での役に立つこちらの参考書が良いでしょう。

この記事のまとめ

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