この記事の内容
デイトラWeb開発コースの課題で以下のようなものがあり、結構手間取ったので実装した方法を残します。
使っている技術とバージョン
- Rails 6.0.6.1
- ruby 2.7.7
- scss
モデルの関係性
- UserモデルにTaskモデルが1対Nの関係で紐づいている
- Userモデルにavatarの画像が保持されている
- CommentsモデルはUserモデル、Taskモデルとそれぞれ1対Nの関係で紐づいている
- UserモデルにはBoardモデルも1対Nの関係で紐づいており、BoardモデルにはTaskモデルが1対Nの関係で紐づいている
得たい成果
-
views/tasks/index.html.haml
上に記事の作成者とコメントした人のアバターを重ねて表示したい
ポイント
- ユーザーをユニーク化して表示する必要がある
- コメントごとにアバターを表示すると、同じユーザーのAvatarが複数回出てしまうため
- showではなくindex画面で表示する必要がある
- showだとtaskが一意に決まるので簡単
- indexだとtasksの配列から要素を取り出して設定する必要があるため手数が増える
実装したコード
task_controller
def index
@tasks = Task.where(board_id: params[:board_id])
@users_with_avatars = {}
@tasks.each do |task|
@users_with_avatars[task.id] = avatars(task)
end
end
private
def avatars(task)
avatars = []
avatars << task.user.id
comments = task.comments.all
comments.each do |comment|
if !avatars.include?(comment.user.id)
avatars << comment.user.id
end
end
avatars
end
tasks/index.html.haml
.card_avatars
- @users_with_avatars[task.id].each do |avatar|
- if User.find(avatar).avatar.attached?
= image_tag task.user.avatar, class: 'card_avatar'
- else
= image_tag 'default-avatar.png', class: 'card_avatar'
詳細
task_controller
private
def avatars(task)
- avatarsメソッドを定義
- indexアクション内での使用のみを想定しているのでpr-ivateで設定
- indexアクション内で@taskを引数にするために(task)を記載
avatars = []
- 後にビュー側でeachメソッドで吐き出せるように配列として定義
- ユニーク化するため、user.idの配列を取得して入れていく
avatars << task.user.id
- タスクの作成者のアバターは必ず入るため
avatars
に追加 - 定義したときに入れてもいいけど、わかりやすさを重視するため別行に記載
comments = task.comments.all
comments.each do |comment|
- taskに紐づいたcommentをすべて取得
if !avatars.include?(comment.user.id)
- コメントした人のユニーク化のための記述
- これがないと、一人の人が複数回コメントすると複数個の同一人物のアバターが表示される
- includeメソッド:配列内に指定した要素があればture、なければfalseを返すメソッド
-
avatars.include?(comment.user.id)
と記述すると配列avatars
内にcomment.user.id
(コメントした人のユーザーID)があればture、なければfalseを返す - 今回の場合、
avatars
の中にすでに同じuser.idがあればfalseにしたいので、!
をつけて論理式を逆にする
avatars << comment.user.id
end
end
- avatars.include?(comment.user.id)がfalseだった場合=
avatars
内にそのuser.idが含まれない場合、avatars
にuser.idを追加する
avatars
end
- これ忘れがち
- メソッドは最後の行の記述をreturnとして返すから、これがないと配列
avatars
を取得できない
def index
@tasks = Task.where(board_id: params[:board_id])
- もともと記載していた内容
- ビュー側で
@tasks.each do
を使って全タスクを表示するため記載していた
@users_with_avatars = {}
- ハッシュを作る
- このあとに
@tasks
からeach do
で取得したtaskを使って、keyにtaskのid、valueに先ほど定義したavatarsメソッドで作る配列とするため - このハッシュがあると、ビュー側の
@tasks.each do |task|
でkeyを指定して、特定のタスクのavatars
(コメントしたユニーク化されたユーザーの配列)を取得できる
@tasks.each do |task|
@users_with_avatars[task.id] = avatars(task)
end
end
- ハッシュ
@users_with_avatars
にtaskのidをkeyとしてavatars
(コメントしたユニーク化されたユーザーの配列)を入れていく
tasks/index.html.haml
.card_avatars
- CSS適用のためのdiv
- CSSはシンプルなので今回は記載しません
- @users_with_avatars[task.id].each do |avatar|
- 裏側で作成した
@users_with_avatars
にキーを渡して、配列avatars
を取得 - user.idが詰まった配列
avatars
なので、user.idを一つ一つ取り出し以下にavatar
として活用
- if User.find(avatar).avatar.attached?
- Userモデルから、idをもとにインスタンスを取得
- 取得したインスタンスにavatarがあるかどうかを判定
- アバターの設定は必須としていないため
= image_tag task.user.avatar, class: 'card_avatar'
- else
= image_tag 'default-avatar.png', class: 'card_avatar'
- アバターが設定されているユーザーならアバターを表示
- そうでないならデフォルトのアバターを表示
- これを
avatar
の要素分繰り返す - 表示されたアバターをCSSで横並びにしてネガティブマージンを使って重ねる
学びになった点
- includeメソッドと!でユニーク化ができる
- @tasksのような配列を使わないといけない状況下の場合、ハッシュを使ってidをキーにして設定するとうまくいく