【50日目】コメントの削除機能、多対多のアソシエーションの設定

# コメントの削除機能
## destroyアクションの作成
コメントコントローラーにdestroyアクションを記載していきます。

```ruby:comments_controller.rb
def destroy
  comment = Comment.find(params[:id])
  comment.delete
  redirect_to comment.board, flash: {notice: 'コメントが削除されました' }
end
```

## コメント削除ボタンの作成
コントローラーでアクションを定義したら、viewを作成します。
link_toの第二引数に渡すパスを調べるために、
http://localhost:3000/rails/info/routesを確認しますと、
deleteアクションのパスはcomment_pathとなっています。
よって下記のようにリンクを作成します。

```ruby:_comments.html.erb
<span><%= link_to '削除', comment, method: :delete, data: { confirm: '削除してもよろしいでしょうか？'} %></span>
# 「link_to 'リンク名', リンク先へのURL又はパス, method: :メソッド名」で指定したメソッドの指定したパスに飛べる'リンク'を作成します。
# methodオプションを指定しない場合、第二引数のパスはGETメソッドのものを参照します。
# data-confirm属性を設定すると、リンクを踏んだ際に確認のダイアログボックスを表示させます。
```

# 編集機能にもエラーメッセージを表示する。
まず前提として、updateアクションには下記のとおりフィルタリングされています

```ruby:boards_controller.rb
before_action :set_target_board, only: %i[show edit update destroy]

# 中略

private

def set_target_board
  @board = Board.find(params[:id])
end
```

これによって、@boardには該当のidのパラメータが渡されております。

その上でupdateアクションを下記のようにします。

```ruby:boards_controller.rb
def update
  if @board.update(board_params)
    flash[:notice] = "「#{@board.title}」の掲示板を編集しました。"
    redirect_to @board
  else
    redirect_to edit_board_path, flash: {
      board: @board,
      error_messages: @board.errors.full_messages
    }
  end
end
```

これによって、パラメータが保存できた場合は編集したことを示すメッセージが表示されて、掲示板のshowにリダイレクトします。
バリデーションエラーでboardが保存できなかった場合は、編集画面にリダイレクトしてエラーメッセージのリストを表示します。
又この時、リダイレクト後も掲示板のフォームには編集時に入力したパラメータは保持されます。



# 多対多のリレーションの考え方
例えば掲示板にタグをつけることを考えます。
ここでは簡略化するため、各テーブルはidとnameのカラムのみを持ちます。

boardsテーブル

| id | name |
|:-:|:-:|
| 1 | １日目 |
| 2 | ２日目 |
| 3 | ３日目 |

tagsテーブル

| id | name |
|:-:|:-:|
| 1 | ruby |
| 2 | SQL |
| 3 | 初心者 |

このとき、例えば
１日目はruby, 初心者
２日目はSQL
３日目はruby, SQL, 初心者
とタグ付けしたかったりした場合、この関係を表そうとすると下記のようになります。

| id | board | tag | tag | tag |
|:-:|:-:|:-:|:-:|:-:|
| 1 | １日目 | ruby | 初心者 | |
| 2 | ２日目 | SQL | | |
| 3 | ３日目 | ruby | SQL | 初心者 |

でもこれではカラムの数が事前に定められない他、カラムに空白が生まれてしまいます。

そこで**「中間テーブル」**を用いて、各id同士を繋げて表します。

| board_id | tag_id |
|:-:|:-:|
| 1 | 1 |
| 1 | 3 |
| 2 | 2 |
| 3 | 1 |
| 3 | 2 |
| 3 | 3 |

このように表現することで、例えば
board_id = 1 である「１日目」には、
tag_id = 1 である「ruby」と
tag_id = ２ である「初心者」がタグづけされていることがわかります。

では以下でこのアソシエーションをどのように実装するかを見ていきます。

# 多対多アソシエーションの実装許容しないようにマイグレーションファイルを書き換える。
まず最初に中間テーブルとtagsテーブルのモデルを作成します。
## テーブルの作成
tagsテーブル

```
docker-compose exec web bundle exec rails g model tag name:string

```

中間テーブル

```
docker-compose exec web bundle exec rails g model board_tag_relation board:references tag:references

```

#### tagがnullであることを許可しない

```ruby:db/migrate/xxxxxx_create_tags.rb
t.string :name, null: false
```

書き換えたら、マイグレーションを実行します。

```
docker-compose exec web bundle exec rake db:migrate
```

## モデルのアソシエーションの設定

####中間モデルの確認
中間モデルは最初からboardモデルとtagモデルにbelongs_toによってassociateされています。
これによって中間モデルはboard_idとtag_idをカラムに持つことになり、上記で示したような２つのidを結びつけることが可能になります。

```ruby:app/model/board_tag_relations.rb
class BoardTagRelation < ApplicationRecord
  belongs_to :board
  belongs_to :tag
end
```

#### tagモデルの修正
タグから掲示板を関連づける設定をする。

```ruby:tag.rb
class Tag < ApplicationRecord
  has_many :board_tag_relations
  has_many :boards, through: :board_tag_relations　# throughオプションによって中間テーブルを経由して複数のboardを持つことを示している。
end
```

#### boardsモデルの修正
掲示板からタグを関連づける設定をする。

```ruby:board.rb
class Board < ApplicationRecord
has_many :comments
has_many :tag_board_relations
has_many :tags, through: :board_tag_relations

validates :name, presence: true, length: { maximum: 10 }
  validates :title, presence: true, length: { maximum: 30 }
  validates :body, presence: true, length: { maximum: 1000 }
end
```

次回はアソシエーションのdependentオプションを作成していきます。



# アソシエーションのdependentオプション
今のhas_manyの関係では、掲示板やタグを削除しても、掲示板とタグとの関係性や、掲示板についたコメントの情報は残ってしまう。
本来であれば掲示板やタグが削除されたら、そのコメントやタグ（掲示板）との関係性は削除されるべきであるから、これを実装する。

#### boardモデルの修正

```ruby:board.rb
class Board < ApplicationRecord
has_many :comments
has_many :tag_board_relations, dependent: :delete_all 
# dependentオプションはdestroyメソッドによってデータを削除した場合に有効になる。deleteメソッドでは無効。
# delete_allとした場合、boardモデルのデータが削除された場合、コメントやtagとの関連付けが全て削除される。
# destroyオプションを使った場合、関連するモデルのオブジェクト一つ一つにdestroyメソッドを使用することになる。
# destroyオプションを使用すれば、関連の先の関連を削除する、依存するデータ全てを削除するなどの複雑なことができる。
# destroyの方が1つ１つやるので動作が重く、delete_allはまとめて削除するのでパフォーマンスが良い。
has_many :tag, through: :tag_board_relations
```

#### tagモデルの修正

```ruby:tag.rb
class Tag < ApplicationRecord
  has_many :board_tag_relations, dependent: :delete_all
  has_many :boards, through: :board_tag_relations
end
```

## 削除機能のdestroyメソッドへの変更

```ruby:board_controller.rb
def destroy
  @board.destroy # deleteメソッドから変更。
  redirect_to boards_path, flash: { notice: "「#{@board.title}の掲示板が削除されました。」"}
end
```