【Ruby on Rails】投稿に紐づくカテゴリーの一覧ページを自作で作成する方法

こんにちは、nishi_talk(@nishi_talk)です。
投稿を管理する、またユーザーが必要としている投稿を探しやすくするようにするために、カテゴリー分けは必須ですよね。
今回は投稿(post)に紐づくカテゴリーの一覧ページを自作で作成する方法をご紹介します。

前提条件

  • ruby 2.5.1
  • Rails 5.2.0

まずお断り

カテゴリー分けるをする方法を検索していたら、いろいろとアンチパターンなるものがあるらしい。
今回ご紹介する方法がベストプラクティス!っていうわけではないので、あくまで一つの方法って感じで試してみて下さい。

今回目指すところ

  • 投稿(posts)と作成するときにカテゴリー(category)は一つだけ選択可能。
  • https://xxxxt/posts → 投稿一覧を取得して表示
  • https://xxxx/posts?category_id=1 → category_idが1に紐付いているの投稿一覧

データベース構造

データベースはこんな感じで設定しました。 メッセージ(posts)
class CreatePosts < ActiveRecord::Migration[5.2]
  def change
    create_table :posts, options: 'ROW_FORMAT=DYNAMIC' do |t|
      t.references :category
      t.text :title, null: false
      t.timestamps
    end
  end
end
カテゴリ(categories)
class CreateCategories < ActiveRecord::Migration[5.2]
  def change
    create_table :categories, options: 'ROW_FORMAT=DYNAMIC' do |t|
      t.string :slug, null: false
    end
  end
end
リレーションは1投稿に1カテゴリーが紐づくように設定しました。
(1)Posts <-> (1)Categories

マイグレーションをしてデータベースを作成

$ rails migrate
データベースはこんな感じになります。
mysql> show columns from posts;
+---------------+--------------+------+-----+---------+----------------+
| Field         | Type         | Null | Key | Default | Extra          |
+---------------+--------------+------+-----+---------+----------------+
| id            | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| category_id   | bigint(20)   | YES  | MUL | NULL    |                |
| title         | text         | NO   |     | NULL    |                |
| created_at    | datetime     | NO   |     | NULL    |                |
| updated_at    | datetime     | NO   |     | NULL    |                |
+---------------+--------------+------+-----+---------+----------------+
 
mysql> show columns from categories;
+-------------+--------------+------+-----+---------+----------------+
| Field       | Type         | Null | Key | Default | Extra          |
+-------------+--------------+------+-----+---------+----------------+
| id          | bigint(20)   | NO   | PRI | NULL    | auto_increment |
| slug        | varchar(255) | NO   |     | NULL    |                |
+-------------+--------------+------+-----+---------+----------------+

routeの設定

投稿が表示できるようにrouteを設定します。
Rails.application.routes.draw do
  resources :posts, param: :id
end

Modelの設定

リレーションをmodel部分に設定します。
今回は1対1なのでbelongs_toとhas_oneでリレーションをはります。
class Post < ApplicationRecord
  belongs_to :category
end
class Category < ApplicationRecord
  has_one :post
end

Controllerの設定

indexにPostの一覧を取得するようにします。
urlにcategory_id(params)がある場合は、category_idで絞った投稿を取得してきます。
それ以外はすべての投稿を取得するようにします。
  • https://xxxxt/posts → 投稿一覧を取得して表示
  • https://xxxx/posts?category_id=1 → category_idが1に紐付いているの投稿一覧
app/controllers/posts_controller.rb
class PostsController < ApplicationController
  def index
    # urlにcategory_id(params)がある場合
    if params[:category_id]
      # Categoryのデータベースのテーブルから一致するidを取得
      @category = Category.find(params[:category_id])
       
      # category_idと紐づく投稿を取得
      @posts = @category.posts.order(created_at: :desc).all
    else
      # 投稿すべてを取得
      @posts = Post.order(created_at: :desc).all
    end
  end
end

Viewの設定

最後に@postのオブジェクトをview側で表示します。 app/views/posts/index.html.erb
<% @posts.each do |item| %>
  <%= item.title %>
<% end %>
あとは、各URLごとにアクセスして、表示できていればOKです。
データベースの知識がまったく無かったので、どうやってCategory_idに紐づく投稿を取得するかがよくわかりませんでしたが、URLで分岐すれば簡単に取得することができました。