##コメント機能
今回、コメントはどのツイートに対してのコメントなのか、誰の投稿したコメントなのかが必要になります。そのため、userモデルとtweetモデルがあるとします。まずはその2つとアソシエーションを実装します。
まずはターミナルでモデルを生成します。
【例】モデル名をComentとします。
$ rails g model comment
##次にマイグレーションの中身を編集します。
【例】
class CreateComments < ActiveRecord::Migration[5.2]
def change
create_table :comments do |t|
t.integer :user_id
t.integer :tweet_id
t.text :text
t.timestamps
end
end
end
上記のように記述すると、コメントの本文はtextカラムに保存していきます。
コメントはまず誰が投稿したコメントなのか分かる必要があるので、結びつくユーザーのidを保存する必要があります。コメントを投稿したユーザーのidを保存するカラムがuser_idとなります。
コメントはどのツイートに対してのコメントなのか明示する必要があり、結びつくツイートのidを保存するカラムがtweet_idとなります。
##次はターミナルで
$ rails db:migrate
を実行します。
##アソシエーション
次はアソシエーションを編集していきます。
userは一人対してtweetはいくつも投稿できるので「1対多」です。
userは一人に対してcommentはいくつも投稿できるので「1対多」です。
tweetはひとつのツイートに対してcommentはいくつも投稿できるので「1対多」です。
アソシエーションを定義していきます。
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
end
class Tweet < ApplicationRecordうに
validates :text, presence: true #バリテーションしてます
belongs_to :user
has_many :comments
end
class Comment < ApplicationRecord
belongs_to :tweet
belongs_to :user
end
上記のように記述します。
ちなみにbelongs_toとhas_manyのアソシエーションの定義は
belongs_to :モデル単数形
has_many :モデル複数形
です。
##ルーティングのネスト
ネストとはある記述の中に入れ子構造で別の記述をする方法です。
ルーティングでネストを利用すると、「あるコントローラーのルーティング内に、別のコントローラーのルーティングを記述すること」となります。
【例】ルーティングのネスト
Rails.application.routes.draw do
resources :親となるコントローラー do
resources :子となるコントローラー
end
end
今回のは、tweets_controller.rbのルーティングの中にcomments_controller.rbのルーティングを記述します。これにより、コメントに結びつくツイートのidの情報を含んだパスを受け取れるようになります。
Rails.application.routes.draw do
devise_for :users
root to: 'tweets#index'
resources :tweets do
resources :comments, only: :create #createアクションしか使わないのでonlyにしてます
end
resources :users, only: :show #showアクションしか使わないのでonlyにしてます
end
上記のようにdoとendで挟むことで中の記述をネストさせました。今回はコメント情報を作る機能しか使わないのでcreateアクションのみにしています。
##commentsコントローラーを生成
ターミナルで
$ rails g controller comments
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
Commentモデルのcreateメソッドの引数では、ストロングパラメーターを用いてカラムを指定しています。
redirect_toの後にはルーティングのURLやPrefixを記述することでそのアクションを実行することができます
tweetsコントローラのshowアクションを実行するにはツイートのidが必要です。
そのため、ストロングパラメーターを用いた上で変数commentに代入します。
textカラムには渡されたparamsの中にcommentというハッシュがある二重構造になっているので、requireメソッドで指定してtextを取り出します。
user_idカラムには、ログインしているユーザーのidとなるcurrent_user.idを保存し、
tweet_idカラムは、paramsで渡されるようにするので、params[:tweet_id]として保存しています。
##コメント投稿用フォームのビュー
【例】
〜省略〜
<div class="XXXX">
<% if current_user %>
<%= form_with(model: [@tweet, @comment], local: true) do |form| %>
<%= form.text_area :text, placeholder: "コメントする", rows: "2" %>
<%= form.submit "送信" %>
<% end %>
<% else %>
<strong><p>※※※ コメントの投稿には新規登録/ログインが必要です ※※※</p></strong>
<% end %>
</div>
〜省略〜
if current_userとすることで、ログインしていればコメント欄を表示し、ログインしていない状態ではテキストを表示するようにしています。
コントローラーも編集していきます。
【例】
class TweetsController < ApplicationController
〜省略〜
def show
@comment = Comment.new
@comments = @tweet.comments.includes(:user)
end
〜省略〜
end
comments#createにアクション先を飛ばしたいので、@comment = Comment.newとインスタンス生成をします。
tweetsテーブルとcommentsテーブルはアソシエーションが組まれているので、@tweet.commentsとすることで、@tweetについて投稿された全てのコメントを取得することができます。
ビューではどのユーザーのコメントかを明らかにするために、アソシエーションを使ってユーザーのレコードを取得する処理をします。includesメソッドはN+1問題解消のために使用しています。
次はtweets/show.html.erbにビューを記述していきます。
〜省略〜
<div class="XXXX">
<h4>コメント一覧</h4>
<% if @comments %>
<% @comments.each do |comment| %>
<p>
<strong><%= link_to comment.user.nickname, "/users/#{comment.user_id}" %>:</strong>
<%= comment.text %>
</p>
<% end %>
<% end %>
</div>
〜省略〜
if @commentsで、もし@commentsが空だった場合でもエラーが起こらないようにしています。
@commentsには複数のコメントが入っているのでeachメソッドを使ってます。
コメントをしたユーザー名をクリックしたらそのユーザーページに遷移するようにlink_toメソッドを使っています。
ユーザーのidはcomment.user_idと記述することで、コメントを投稿したユーザーのidをparamsとして送ります。