【Rails】データを削除する際、関連レコードがある場合にアラートを出す

# 概要
* `dependent: :restrict_with_error` をつける
* `redirect_to`のnoticeに`errors.messages[:base]`を表示する
![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/19e8d69f-67b9-9c51-4042-7aa465a3d115.png)

# 目的
has_many関連レコードが設定されている時、関連レコードが設定されている場合は削除しないようにする


# 手順
現在運営している学会検索のイベントサイト[dokode](https://www.dokode.work)を運営しています。その中で、イベントページ（Event）は下記の関係を持っています
Eventにはイベントの情報、EventVenueにはイベントの場所情報が入っています。
今回は、Eventのデータ削除時にEventVenueにデータがあった場合は削除せずアラートを出すようにします。

![alt](http://www.plantuml.com/plantuml/png/SoWkIImgAStDuKhDAyaigLH8BGCoYXMgkNHrkV8ISrEvQb5aue54QIfK7YAIcG9Jo0NLh5kDefZB8JKl1MW50000)

手順は以下の通りです。

1. Eventのhas_manyのdependentオプションに`:restrict_with_error`をつける
2. controllerのdestroyアクションを修正する
3. 日本語化する

## has_manyのdependentオプションに`:restrict_with_error`をつける

親がEventで子がEventVenueです。Eventモデルのhas_manyを以下のように修正します。

```app/models/event.rb
class Event < ApplicationRecord
  has_many :event_venues, dependent: :restrict_with_error
```

restrict_with_errorについて[RAILS GUIDES](https://railsguides.jp/association_basics.html#has-one%E3%81%AE%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3-dependent)の説明をみると以下の通りです。

> 4.2.2.4 :dependent
> オーナーオブジェクトがdestroyされた時に、それに関連付けられたオブジェクトをどうするかを制御します。
> 省略
> :restrict_with_errorを指定すると、関連付けられたオブジェクトがある場合にエラーがオーナーに追加されます。

今回の事例では、いかが対応します。

* 関連付けられたオブジェクト=EventVenue。
* オーナー=Event

エラーというのは、event.errorsのことです。validationで使うやつです。[restrict_with_error箇所のソース](https://github.com/rails/rails/blob/b2eb1d1c55a59fee1e6c4cba7030d8ceb524267c/activerecord/lib/active_record/associations/has_many_association.rb#L21)を見ると、 `:base` にエラーメッセージが追加されることがわかります。

```rails/activerecord/lib/active_record/associations/has_many_association.rb
owner.errors.add(:base, :'restrict_dependent_destroy.has_many', record: record)
```

event.errors.messages[:base]でエラーメッセージを呼び出すことができます。

## controllerのdestroyアクションを修正する
上記で重要なことは話終えたので、あとは、実際に実装した例を示します。
削除したときと言うことで、呼び出されるのは コントローラの `destory` メソッドですので、こちらを呼び出すモジュールを下記の通実装しました。


```app/controllers/concerns/custom_destroy.rb
module CustomDestroy
  extend ActiveSupport::Concern

  included do
    def destroy
      model = controller_name.classify.constantize # コントローラ名からモデルを作る
      record = model.find(params[:id]) # モデルからレコードを呼び出す。
      record.destroy # 削除命令
      if record.errors.present? # 削除が失敗した場合
        redirect_to url_for(controller: controller_name, action: :index), notice: "#{record.errors.messages[:base].join('。')}" and return # 削除が失敗した場合は、notificationに関連レコードのモデル名を表示する
      else # 削除が成功した場合
        redirect_to url_for(controller: controller_name, action: :index), notice: "#{record.model_name.human}は削除されました" and return # 削除が成功した場合は、notificationにレコード自身のモデル名を表示する
      end
    end
  end
end
```

上記のモジュールのdestroyを導入したいコントローラでincludeします。
今回はEventのコントローラーに導入します。なお、destoryアクションが既に導入されている場合は消して下さい。

```app/controllers/events_controller.rb
class EventsController < ApplicationController
  include CustomDestroy # これで 上記のモジュールを読みこまれるので、このコントローラーにdestoryメソッドが導入されます。
```

表示は下記のようなヘルパーメソッドを追加して下さい。

```views/xxxx.html.erb
<%= content_tag :div, flash['notice'], class: "alert-dismissible alert alert-info" %>
```
これで削除(destory)を呼び出すと下記のような表示が行われます。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/124445/c3c62902-0533-164a-d961-92b5f81ae8b7.png)


## 日本語化する
上記までで、動きは完成です。ただ、関連モデル名が英語のままですので、日本語化します。
日本語化するときは、下記の通り追加して下さい。

```config/locales/ja.yml
ja:
  activerecord:
    attributes:
      event:
        event_venues: イベント場所 # ←これ
```

# 結果
[dokode](https://www.dokode.work)の管理者用のイベント情報管理ページに導入した例です。

[![Image from Gyazo](https://i.gyazo.com/3a786b82fab759409aab605c9e42f28f.gif)](https://gyazo.com/3a786b82fab759409aab605c9e42f28f)

以上です。