【Rails】json形式のレスポンスを整形して、画像ファイルのURLをフロントに渡す方法

## はじめに
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がある

## やり方
### 方向性
1. ruby側で`comment.user.profile.avatar`　のPathを動的に生成
2. 上記のpathも含めたjson形式のレスポンスを作成し、javascript側へ渡す
3. json形式のレスポンスを展開し、htmlとして出力。その際にimgタグを生成し、src属性に画像のpathを動的に記述する

### `comment.user.profile.avatar`　のPath生成方法
Profileモデルに画像のPathを生成するためのメソッドを定義する
```ruby
# 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を作成します
```bash
rails generate serializer comment
rails generate serializer user
rails generate serializer profile
```

次に各serializerファイルに下記のように記述します。
```ruby
# 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形式でレンダリングするように設定します。
```ruby
# 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ファイルと同じように、モデル間の関係性を記述することで、出力の形を整形できます。
今回のような記述内容だと、下記のように出力されます。
```json
{
  "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を生成する
```javascript
$(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形式でレスポンスを返したいことは多くなりそうなので、整形の仕方は覚えておこうともいますー！