【備忘録】【Rails】多対多の関係の実装（カテゴリ編）

自分用のメモです。
Railsで多対多の関係を実装する手順を残しておきます。

# モデル
記事投稿の情報をもつPostモデル、
カテゴリの情報を持つCategoryモデル、
その2つを繋ぐ中間テーブルとしてPost_Category_Relationモデルを作成します。

1つのPostは複数のCategoryを持ちえ、1つのCategoryは複数のPostを持つ関係とします。

# 多対多を実装した経緯

PostとCategoryが "複数 - 複数" 、つまり多対多の関係になっており、
中間テーブルというものを作りデータを管理する必要が出てきたため。

# 中間テーブルの関係性を表した図

![スクリーンショット 2021-05-23 23.22.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/985164/9789e6ce-aed8-165a-b91b-c045db7116c7.png)


# 下準備

```ruby:ターミナル
$ bundle exec rails g model Post user_id:integer title:string content:text category_id:integer

$ bundle exec rails g model Category name:string

$ bundle exec rails g model Post_category_relation post:references category:references

$ bundle exec rails db:migrate（データベースに変更を指示・反映する）

```

これにより、
・app/modelsフォルダ下に各モデルファイルが生成（app/models/モデル名.rb）
・db/migrateフォルダ下に各マイグレーションファイルが生成

# 各モデルファイルを編集

Post_category_relationモデルにはbelongs_toが自動で設定されていますが、
PostモデルとCategoryモデルには手動で追加する必要がある。

多対多の形での実装では、中間テーブルとのhas_many関係の記述と、
その中間テーブルを通したPostモデル、Categoryモデルとの紐付けという
2つのhas_many throughを記述する必要がある。


```ruby:app/models/post.rb
class Post < ApplicationRecord
  has_many :post_category_relations
  has_many :categories, through: :post_category_relations
end
```

```ruby:app/models/category.rb
class Category < ApplicationRecord
  has_many :post_category_relations
  has_many :posts, through: :post_category_relations
end
```

```ruby:app/models/post_category_relation.rb
class PostCategoryRelation < ApplicationRecord
  belongs_to :post
  belongs_to :category
end
```

モデルの実装は以上。

# Controllerの実装

新規投稿すると、その投稿の詳細ページに遷移する作りにしています。
投稿に紐付くカテゴリは、投稿を登録する際のパラメータに含まれるため、strong parametersを使用しています。

```ruby:app/models/posts_controller.rb
class PostsController < ApplicationController
  def new
    @post = Post.new
  end

  def create
    @post = Post.create(post_params)
    redirect_to @post
  end

  def show
    @post = Post.find(params[:id])
  end

  private

  def post_params
    params.require(:post).permit(:title, :content, category_ids: [])
  end
end

```
ポイントは、post_paramsメソッドのcategory_ids: []です。
投稿に紐付くカテゴリは、チェックボックスによって複数渡される場合があるため、配列形式であることを記載します。



# Viewsの実装

新規投稿する画面です。
前述の多対多のアソシエーションにより、postオブジェクトにcategory_idsというプロパティが追加されます。

```ruby:app/views/posts/new.html.erb
<%= form_with model: @post do |f| %>
  <p>
    <%= f.label :title, 'タイトル' %>
    <%= f.text_field :title %>
  </p>

  <p>
    <%= f.label :content, '本文'%>
    <%= f.text_field :content %>
  </p>

  <p>
    <%= f.label :category, 'カテゴリ' %>
    <%= f.collection_check_boxes(:category_ids, Category.all, :id, :name) do |category| %> 
      <%= category.label do %>
        <%= category.check_box %>
        <%= category.text %>
      <% end %>
    <% end %>
  </p>

  <%= f.submit '投稿する' %>
<% end %>
```

存在するカテゴリの数だけチェックボックスを作成するために、collection_check_boxesメソッドを使用します。引数の内容は以下のとおりです。

第一引数 :category_ids postオブジェクトのプロパティ名
第二引数 Category.all　categoryオブジェクトの配列を取得
第三引数 :id　チェックボックスのvalue属性の値
第四引数 :name チェックボックスのラベル名


# その他

サンプルとして、コンソールからカテゴリ名を登録しておきます。
$ rails cでコンソールを起動して、以下のコードを入力します。

```ruby:コンソール
%W[仕事 勉強 家族 恋愛 生活 お金 障害 人間関係 ペット その他].each { |sample| Category.create(name: sample) }
```


投稿登録後に遷移するviewファイル（投稿の詳細ページ）は以下を使用します。

```ruby:app/views/posts/show.html.erb
<p><%= "【タイトル】#{@post.title}" %></p>
<p><%= "【本文】#{@post.content}" %></p>
<span>【カテゴリ】</span>
<% @post.categories.each do |category| %>
  <%= category.name %>
<% end %>
```

ルーティングは以下のとおり。

```ruby:config/routes.rb　
Rails.application.routes.draw do
  resources :posts, only: [:show, :new, :create]
end
```

# 最後に

$ rails sでサーバ起動後、
http://localhost:3000/posts/new にアクセスして新規投稿画面を表示し、正しく表示されているか確認しておしまい！

▼参考記事▼

https://qiita.com/cawaz3/items/e755a58177212f2aca6c

大変参考になりました！ありがとうございました！！