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


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

モデル

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

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

多対多を実装した経緯

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

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

下準備

ターミナル
$ 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を記述する必要がある。

app/models/post.rb
class Post < ApplicationRecord
  has_many :post_category_relations
  has_many :categories, through: :post_category_relations
end
app/models/category.rb
class Category < ApplicationRecord
  has_many :post_category_relations
  has_many :posts, through: :post_category_relations
end
app/models/post_category_relation.rb
class PostCategoryRelation < ApplicationRecord
  belongs_to :post
  belongs_to :category
end

モデルの実装は以上。

Controllerの実装

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

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というプロパティが追加されます。

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でコンソールを起動して、以下のコードを入力します。

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

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

app/views/posts/show.html.erb
<p><%= "【タイトル】#{@post.title}" %></p>
<p><%= "【本文】#{@post.content}" %></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 にアクセスして新規投稿画面を表示し、正しく表示されているか確認しておしまい!

▼参考記事▼

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