はじめに
Ajaxでコメント一覧の表示・コメント投稿の機能を実装する際に、コメントした人のアバターも入れたかったのですが、やり方が分からなくて苦戦したので実装方法を残しておきます。
やりたいこと
javascriptからaxios.getでリクエストを送った際に下記の情報がほしい
- commentの内容
- commentした人のusername
- commentした人のavatar
状況
- 関連するモデル
- Userモデル
- Commentモデル
- Proflieモデル
- 各モデルの関連性
- User has_many commnets
- User has_one profile
- Post has_many commnets
- 各モデルに紐づく要素(今回の件に関係するところのみ)
- profilesテーブルにavatar画像がある
- commentsテーブルにcontent(コメントの内容)がある
- usersテーブルにusernameがある
やり方
方向性
- ruby側で
comment.user.profile.avatar
のPathを動的に生成 - 上記のpathも含めたjson形式のレスポンスを作成し、javascript側へ渡す
- json形式のレスポンスを展開し、htmlとして出力。その際にimgタグを生成し、src属性に画像のpathを動的に記述する
comment.user.profile.avatar
のPath生成方法
Profileモデルに画像のPathを生成するためのメソッドを定義する
# app/models/profile.rb
class Profile < ApplicationRecord
has_one_attached :avatar
def avatar_path
Rails.application.routes.url_helpers.rails_blob_path(avatar) if avatar.attached?
end
end
ポイント
Rails.application.routes.url_helpers
rails_blob_urlメソッドを使ってURLを生成したいのですが、Controllerやview以外だと上記のようにアクセスする必要があるようです。
rails_blob_path(avatar)
rails_blob_path()
はActiveStorageで使えるメソッドです。
()内の画像の相対パスを返してくれます。
ちなみに、絶対Path(URL)と相対Pathどちらがいいかでも悩んだのですが、今回は外部から直接画像にアクセスする必要がなく、アプリケーション内部での参照をする想定なので相対Pathを選択しました。
avatar_path(avatar)
のように引数を設定しなくてもいい理由
微妙に悩んだのですが、引数の設定は不要でした。
理由は、ruby側でcurrent_user.profile.avatar
でavatarを参照できるように、json形式のレスポンスをシリアライザーで作成するときもavatarの参照が可能なためです。
この辺の関数やメソッドの引数の考え方についてはまだまだ理解不足なので精進します。。。
json形式のレスポンスの整形方法
前提としてActiveSerializerを導入しているものとします。
まず関連するモデルのSerializerを作成します
rails generate serializer comment
rails generate serializer user
rails generate serializer profile
次に各serializerファイルに下記のように記述します。
# app/serializers/comment_serializer.rb
class CommentSerializer < ActiveModel::Serializer
attributes :id, :content
belongs_to :user
end
# app/serializers/user_serializer.rb
class UserSerializer < ActiveModel::Serializer
attributes :id, :name
has_one :profile
end
# app/serializers/profile_serializer.rb
class ProfileSerializer < ActiveModel::Serializer
attributes :avatar_path
end
Controller側でjson形式でレンダリングするように設定します。
# app/controllers/comments_controller.rb
def index
post = Post.find(params[:post_id])
comments = post.comments
render json: comments, include: ['user.profile']
end
ポイント
serializerファイルの記述方法
attributes
にカラム名を指定することで、そのカラムの情報を出力できます。
また、メソッドを指定することもでき、その場合メソッドの返り値を出力できます。
Modelファイルと同じように、モデル間の関係性を記述することで、出力の形を整形できます。
今回のような記述内容だと、下記のように出力されます。
{
"id": 1,
"content": "hogehoge",
"user": {
"id": 1,
"name": "hoge hoge",
"profile": {
"avatar_path": "/rails/active_storage/blobs/12345/user_avatar.png"
}
}
}
わりとシンプル!
render json: comments, include: ['user.profile']
について
本来は、include以下はいらないはずなのですが、
今回はinclude以下を記述しないとproflieをアウトプットに含めてくれませんでした。
この辺の仕様がどうなっているかはわからなかったのですが、今回のように明示する必要があるケースもありそうです。
JavaScript側でhtmlを生成する
$(document).on('turbolinks:load', () => {
const postId = $('#commentTitle').data('post-id')
axios.get(`/posts/${postId}/comments`)
.then(response => {
const comments = response.data
comments.forEach(comment => {
const commentHtml = `
<div class="comment">
<img src="${comment.user.profile.avatar_path}" alt="User Avatar">
<div class="commentContent">${comment.content}</div>
</div>
`
$('.comments-container').append(commentHtml)
})
})
こんな感じでimgのsrc属性に取得したPathを指定することでうまく表示されました!
おわりに
json形式でレスポンスを返したいことは多くなりそうなので、整形の仕方は覚えておこうともいますー!