Railsで投稿とカテゴリの紐付け機能を実装する

# はじめに

Ruby on Railsで、投稿（Post）とカテゴリ（Category）の紐付けを実装します。
モデルやコントローラの生成は割愛します。予めご了承ください。

実行環境
- Ruby 2.5.1
- Rails 5.2.3

# 完成イメージ
1.投稿に紐付けるカテゴリは、チェックボックスから複数選択できます。
![ScreenShot 2019-07-15 11.43.39.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/460297/c4fb66b7-77b3-a51e-0013-9a3707f30b35.jpeg)

2.「投稿する」ボタンをクリックすると、DBへの登録とページ遷移が行われます。
![ScreenShot 2019-07-15 11.47.46.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/460297/3ebe16b4-f805-3006-1b51-24e4185fca73.jpeg)


# 多対多のアソシエーション（関連付け）
`Post`は複数の`Category`に紐付き、`Category`も複数の`Post`に紐付きます。これを**多対多の関係**といいます。
多対多の関係は、**中間テーブル**を利用して実現します。
<img width="752" alt="ScreenShot 2019-07-12 20.30.23.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/460297/787f7fbd-0e78-af37-5abf-ce9f7ee37b7a.jpeg">

中央の`Post_category_relations`が中間テーブルです。紐付けるモデルの`post_id`と`category_id`を持ちます。


## モデル

まずは中間テーブルのモデルから。
`Post`と`Category`を`belongs_to`で関連付けます。

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


続いて、`Post`と`Category`のモデルです。
中間テーブルのモデルである`post_category_relations`を`has_many`で関連付けます。

```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
```
また、`Post`は`Category`を、`Category`は`Post`を`has_many`で関連付けます。
ここでポイントとなるのが、**`through: :post_category_relations`**です。
中間テーブルの`post_category_relations`を経由して、関連付けをすることを意味しています。
**なお、`has_many`の関連付けは、中間テーブルを先に記述しないと`ActiveRecord::HasManyThroughOrderError`というエラーが発生しますので、注意してください。**

# ビュー
投稿を登録する画面です。
前述の多対多のアソシエーションにより、`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 :body, '本文'%>
    <%= f.text_field :body %>
  </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` チェックボックスのラベル名

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

```ruby:app/controller/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, :body, category_ids: [])
  end
end
```

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

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

```
%W[Ruby Java Python Go].each { |sample| Category.create(name: sample) }
```

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

```ruby:app/views/posts/show.html.erb
<p><%= "【題目】#{@post.title}" %></p>
<p><%= "【本文】#{@post.body}" %></p>
<span>【カテゴリ】</span>
<% @post.categories.each do |category| %>
  <%= category.name %>
<% end %>
```
ルーティングは以下のとおり。

```config/routes.rb　
Rails.application.routes.draw do
  resources :posts, only: [:show, :new, :create]
end
```
#終わりに

`$ rails s`でサーバ起動後、`http://localhost:3000/posts/new`にアクセスして投稿登録画面を表示します。
完成イメージ通りに、投稿とカテゴリを紐付けた登録ができるでしょう👏