はじめに
Railsで投稿機能を作っていると、「最初に作った投稿(親投稿)」に対して「その後の経過(子投稿)」を紐づけて記録したいケースが出てきます。
例えば、商品レビューの途中経過を追加したり、使い始めてからの変化を追跡したりするようなイメージです。編集とは違い経過を記録するために作りました。
ここでは、親子関係を持った投稿機能(親:Item、子:ItemPost) を実装する流れをまとめます。
これまでの記事はこちら👇
ポートフォリオ構築の振り返り(第1回:プロジェクト概要と設計)
ポートフォリオ構築の振り返り(第2回:Railsアプリ立ち上げ〜トップページ表示)
ポートフォリオ構築の振り返り(第3回:Deviseでログイン機能を実装)
ポートフォリオ構築の振り返り(第4回:ヘッダーの作成とログイン機能の実装)
ポートフォリオ構築の振り返り(第5回:投稿機能と画像投稿フォームの作成)
ポートフォリオ構築の振り返り(第6回:投稿機能の作成)
ポートフォリオ構築の振り返り(第7回:ユーザーカラム追加)
ポートフォリオ構築の振り返り(第8回:部分テンプレートを使ったマイページ作成)
ポートフォリオ構築の振り返り(第9回:itemのカード表示と共通化)
今回の流れ
- モデルの作成(親:Item、子:ItemPost)
- コントローラの作成
- ビューでの親子関係を考慮した表示・投稿
- まとめと用語解説
内容
1. 親子関係の考え方
- 親投稿(Item):元となる投稿(商品やレビュー対象)
- 子投稿(ItemPost):親に紐づく追加の経過投稿(途中経過や再レビュー)
これにより、1つの投稿のライフサイクルを継続的に追跡できるようになります。
親に依存して存在するのが子というのが重要なポイントです。
2. モデルの定義
app/models/item.rb(親)
class Item < ApplicationRecord
has_one_attached :image
validates :title, presence: true, length: { maximum: 100 }
validates :body, presence: true, length: { maximum: 500 }
validates :category, presence: true
validates :image, presence: true
has_many :item_posts, dependent: :destroy
belongs_to :user
belongs_to :group, optional: true
enum category: { cosmetics: 0, daily_necessities: 1, groceries: 2, supplement: 3 }
enum status: { unopened: 0, start: 1, active: 2, finish: 3, discard: 4, repeat: 5 }
scope :latest, -> { order(created_at: :desc) }
end
app/models/item_post.rb(子)
class ItemPost < ApplicationRecord
has_one_attached :image
belongs_to :user
belongs_to :item
accepts_nested_attributes_for :item
validates :review, presence: true, length: { maximum: 500 }
validates :image, presence: true
enum status: { unopened: 0, start: 1, active: 2, finish: 3, discard: 4, repeat: 5 }
end
✅ ここで重要なのは以下です:
-
Item has_many :item_posts… 親に複数の子が紐づく -
ItemPost belongs_to :item… 子は必ず親に紐づく
3. コントローラの実装
app/controllers/public/items_controller.rb(一部)
class Public::ItemsController < ApplicationController
def show
@item = Item.find(params[:id])
@item_post = ItemPost.new
end
end
app/controllers/public/item_posts_controller.rb(一部)
class Public::ItemPostsController < ApplicationController
def new
@item = Item.find(params[:item_id])
@item_post = ItemPost.new(item_id: @item.id)
end
def create
@item = Item.find(params[:item_id])
@item_post = current_user.item_posts.new(item_post_params)
@item_post.item = @item
if @item_post.save
redirect_to item_path(@item), notice: '経過投稿を追加しました。'
else
render :new
end
end
private
def item_post_params
params.require(:item_post).permit(:review, :image, :status, :item_id, item_attributes: [:star, :deadline])
end
end
✅ ここでのポイント:
-
ItemPostsControllerではparams[:item_id]から親を取得 - 子投稿を
@item_post.item = @itemで親に紐づけて保存
4. ビューでの実装例
親投稿の詳細ページ(app/views/public/items/show.html.erb)
<h2><%= @item.title %></h2>
<p><%= @item.body %></p>
<h3>経過投稿</h3>
<% @item.item_posts.each do |post| %>
<div class="child-post">
<p><%= post.review %></p>
<%= image_tag post.image if post.image.attached? %>
</div>
<% end %>
<h3>経過投稿を追加</h3>
<%= form_with model: [@item, @item_post], local: true do |f| %>
<div>
<%= f.label :review, "レビュー" %>
<%= f.text_area :review %>
</div>
<div>
<%= f.label :image, "画像" %>
<%= f.file_field :image %>
</div>
<div>
<%= f.label :status, "ステータス" %>
<%= f.select :status, ItemPost.statuses.keys.map { |k| [k, k] } %>
</div>
<%= f.submit "投稿する" %>
<% end %>
✅ ポイント:
-
@item.item_posts.eachで親に紐づく子を一覧表示 -
form_with model: [@item, @item_post]で子投稿を親に紐づけて作成
まとめ
- 親投稿(Item)と子投稿(ItemPost)の親子関係を作ることで、1つの投稿の経過を追加できるようにした
- モデルで
has_manyとbelongs_toを設定するのが基本 - コントローラで
params[:item_id]を受け取り、子に親を紐づけるのがポイント - ビューでは
[親, 子]の形でフォームを組むことで親子関係を維持できる
👉 これで、投稿の親子関係(親:Item、子:ItemPost)を作り、親に対して経過投稿を追加できるようになりました!
次回は個人投稿だけじゃなくグループで投稿や管理ができるような仕組みを振り返ります!
ポートフォリオ構築の振り返り(第11回:グループ機能を作る)
用語説明
-
親子関係 … データ同士が「1対多」で紐づく関係。Railsでは
has_many(親)とbelongs_to(子)で表現する。 - accepts_nested_attributes_for … 子モデルの属性を親モデルのフォームから更新できる仕組み。
-
form_with model: [親, 子] … ネストしたリソース(例:
/items/:item_id/item_posts)に対応する書き方。 - dependent: :destroy … 親が削除されたとき、関連する子も一緒に削除するオプション。