コメント機能の実装です
正直ここはすごく苦手です。。。
Xの素晴らしい出来に改めて尊敬です!
目的
- アソシエーションを復習
- ルーティングのネストの理解
コメントは、ツイートと別のテーブルで管理しなくてはなりません。
なのでコメントテーブルを作る必要があります
さらに、コメントはどの投稿に対してのコメントなのか、
誰の投稿したコメントなのか明示されている必要があります
そのため、userモデルとtweetモデルの2つにアソシエーションを組む必要があります
Commentモデル作成
rails g model comment
マイグレーション編集
db/migrate/20XXXXXXXXXXXX_create_comments.rb
lass CreateComments < ActiveRecord::Migration[7.0]
def change
create_table :comments do |t|
t.integer :user_id
t.integer :tweet_id
t.text :text
t.timestamps
end
end
end
「誰が投稿したコメントなのか」user_id
「どのツイートに対してのコメントなのか」tweet_id
rails db:migrate
tweet 「1対多」 comment
users 「1対多」 comment
アソシエーション定義
app/models/comment.rb
class Comment < ApplicationRecord
belings_to :tweet
belongs_to :user
end
app/models/tweet.rb
lass Tweet < ApplicationRecord
validates :text, presence: true
belongs_to :user
has_many :comments # commentsテーブルとのアソシエーション
end
app/models/user.rb
class User < ApplicationRecord
# Include default devise modules. Others available are:
# :confirmable, :lockable, :timeoutable and :omniauthable
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :validatable
has_many :tweets
has_many :comments # commentsテーブルとのアソシエーション
end
createアクションのルーティングを設定
ルーティングのネスト
ある記述の中に別の記述をして、親子関係を示す方法「入れ子構造」
ルーティングをネストさせる一番の理由は、アソシエーション先のレコードのidをparamsに追加してコントローラーに送るためです
config/routes.rb
Rails.application.routes.draw do
devise_for :users
root to: 'tweets#index'
resources :tweets do
resources :comments, only: :create
end
resources :users, only: :show
end
tweet_comments POST /tweets/:tweet_id/comments(.:format) comments#create
:tweet_id
の箇所へ、コメントと結びつくツイートのidを記述すると、paramsの中にtweet_id
というキーでパラメーターが追加され、コントローラーで扱うことができます
commentsコントローラーを作成
rails g controller comments
createアクションをコントローラーに定義
app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
Comment.create(comment_params)
end
private
def comment_params
params.require(:comment).permit(:text).merge(user_id: current_user.id, tweet_id: params[:tweet_id])
end
end
commentモデルのcreateメソッドの引数では、ストロングパラメーターを用いて保存できるからむが指定しています
渡されたparamsの中にcommentというハッシュがある二重構造になっているため、requireメソッドの引数に指定して、textを取り出しました。
user_idカラムには、ログインしているユーザーのidとなるcurrent_user.id
を保存し、
tweer_idカラムは、paramsで渡されるようにするので、params[:tweet_id]
として保存
app/controllers/comments_controller.rb
class CommentsController < ApplicationController
def create
comment = Comment.create(comment_params)
redirect_to "/tweets/#{comment.tweet.id}" # コメントと結びつくツイートの詳細画面に遷移する
end
private
def comment_params
params.require(:comment).permit(:text).merge(user_id: current_user.id, tweet_id: params[:tweet_id])
end
end
redirect_toの後に亜h、ルーティングのURLやPrefixを記述することで、そのアクションを実行できます
tweetsコントローラーのshowアクションを実行するには、ツイートidが必要です
そのため、ストロングパラメーターを用いて上で変数commentに代入します
リダイレクト先の指定には、アソシエーションを利用して、commentと結びつくツイートのidを記述しています
コメント投稿フォームを投稿詳細に追加
app/views/tweets/show.html.erb
<div class="contents row">
<p><%= @nickname %>さんの投稿一覧</p>
<% @tweets.each do |tweet| %>
<%= render partial: "tweets/tweet", locals: { tweet: tweet } %>
<% end %>
<div class="container">
<% if user_signed_in? %>
<%= form_with(model: [@tweet, @comment], local: true) do |form| %>
<%= form.text_area :text, placceholder: "コメントする"、rows: "2" %>
<%= form.submit "SEND" %>
<% end %>
<% else %>
<strong><p>コメントの投稿には新規登録/ログインが必要です</p></strong>
<% end %>
</div>
</div>
条件分岐により、ログインしていない状態では投稿フォームを出さずにテキストを表示するようにしています。
コメント表示欄を投稿詳細に追加
ツイートの詳細画面でツイートと結びつくコメントを表示するためには、ビューを呼び出す前のコントローラーが実行されている時点で、まずはコメントのレコードをデータベースから取得する必要があります
app/controllers/tweets_controller.rb
~略~
def show
@comment = Comment.new
@comments = @tweet.comments.includes(:user)
end
~略~
tweets/show.html.erb
でform_withを使用して、comments#create
を実行するリクエストを飛ばしたいので、@comment = Comment.new
というインスタンス変数を生成しなければなりません。
tweetsテーブルとcommentsテーブルはアソシエーションが組まれているので、@tweet.comments
とすることで、@tweet
へ投稿された全てのコメントを取得
アソシエーションを使ってユーザーのレコードそ取得する処理を繰り返します。
その時に「N+1問題」が発生してしまうので、includesメソッド
を使って、N+1問題を解決している点にも注意
app/views/tweets/show.html.erb
<div class="comments">
<h4><コメント一覧></h4>
<% @comments.each do |comment| %>
<p>
<strong><%= link_to comment.user.nickname, "/users/#{comment.user_id}" %>:</strong>
<%= comment.text %>
</p>
<% end %>
</div>
@comments
には複数のコメントのレコードが入っているので、配列の形をとっています。
そのため、ビューに表示させるためにはeachメソッド
を使って、一つ一つのレコードを分解してから表示させます。
コメントしたユーザーの名前をクリックしたら、そのユーザーのマイページへ遷移します。
そのために、名前のところへlink_toメソッド
を使ってリンクを作りました
ユーザーのidはcomment.user_id
とパスに記述することで、コメントを投稿したユーザーのidをparamsで扱えるようにしてます