0
1

投稿アプリのコメント機能

Posted at

コメント機能の実装です
正直ここはすごく苦手です。。。
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で扱えるようにしてます

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1