はじめに
現在、ポートフォリオとして掲示板アプリを作成しており、題目にある「TopicとPostを紐づける」部分で時間かかったので備忘録として。
早速ですが、紐づけるために必要な要素として以下の2点を中心に記載します。
- model側の修正
- controller側の修正
やりたいこと
複数あるPostは必ず一つのTopicに紐づく
PostやTopicは必ず一つのUserに紐づく
各modelを関連付けて、「@post.topic.title」のような形で値を取得したい
環境
macOS:10.14.6
Ruby:2.5.7
Rails:5.2.4.1
model側の修正(アソシエーション)
今回登場するmodelは、以下の3つ。
・Topic
・User(deviseを使ってます)
・Post
Topicがスレッド、Postがスレッドに対するレスで、
Topic(1)---(多)Post
User(1)---(多)Post/Topic
というような位置付けになります。
今回の場合は既に各modelを作成(generate)済みだったので、アソシエーション用のマイグレーションファイルを作って、マイグレートしていく形で進めます。
マイグレーションファイルを作成する
$ rails g migration AddUserToTopic
$ rails g migration AddTopicToPost
マイグレーションファイルを編集する
文法は、「add_reference table名、reference名」が基本形になります。
indexはオプションなのでお好みで。
ちなみに超ざっくりですが、indexは読み込み/取得速度を上げてくれるものです
(但し、書き込み速度は遅くなるので注意が必要)
class AddUserToTopic < ActiveRecord::Migration[5.2]
def change
add_reference :topics, :user, index: true #追記箇所
end
end
class AddTopicToPost < ActiveRecord::Migration[5.2]
def change
add_reference :posts,:topic #追記箇所
end
end
マイグレートする
$ rails db:migrate
上記コマンド実行後、以下のようなmigratedが出力されればOK
== 20200223032448 AddTopicToPost: migrating ===================================
-- add_reference(:posts, :topic)
-> 0.0796s
== 20200223032448 AddTopicToPost: migrated (0.0797s) ==========================
モデルを編集する
class Topic < ApplicationRecord
validates :title, presence: true
has_many :posts #追記
belongs_to :user #追記
end
class Post < ApplicationRecord
belongs_to :topic #追記
belongs_to :user #追記
end
class User < ApplicationRecord
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
validates :name,:email,:encrypted_password, presence: true
has_many :topics, through: :posts #追記
end
これでmodel側はOKです!
controller側の修正
各modelの関連付けできたので以下のような形で、関連先の値を拾ってこれるようになりました。細かく言うとパラメーターの中から明示的に値を取得するイメージです。
params[:topic_id]という風な感じで。
class PostsController < ApplicationController
def new
@topic_id = params[:topic_id]
@post = Post.new
end
def create
@post = Post.new
@topic_id = params[:topic_id] #paramsからtopic_idを取得し、インスタンス変数@topic_idに代入
@post.topic_id = @topic_id #上記を@post.topic_idに代入
@post.body = params[:post][:body]
@topic = Topic.find(@topic_id)
@post.user_id = current_user.id
if @post.save
redirect_to topic_path(@topic_id), notice: '投稿しました'
else
render 'posts/new', alert: '投稿できませんでした'
end
end
end
ちょっと汚いですね、、、汗
取得したい値は「paramsから取得してインスタンスに代入」と書く形です。
なので、取得したい値に応じて編集してください。上記はあくまで参考として。
viewの修正(おまけ)
以下はトピック一覧画面(topic#index)ですが、postの数やpostの投稿時間を取得して表示することができます。
<div class="container mt-5 ml-5">
<div class="row">
<% @topics.each do |topic| %>
<div class="table tabel-hover">
<%= link_to topic do %>
<div class="list-group" style="max-width: 500px;">
<div class="list-group-item list-group-item-action">
<%= topic.title %> (<%= topic.posts.count %>) #post数を計算
<br>
<small class="text-muted text-right">
最終投稿日時:<%= topic.posts.last.created_at %> #最新のpost投稿時間を取得する
</small>
</div>
<% end %>
</div>
<% end %>
</div>
</div>
</div>
こんな表示になります↓
非常に便利ですね。