kent備忘ログ

お仕事と趣味生活と

2月の振り返り(deviseユーザプロフィール変更・同一テーブルの自己結合・同一ビュー内での一覧・詳細画面の表示)

先月に続いてRailstwitterのようなSNSアプリ制作でしたが、実装を通してrubyの文法の再確認やRailsの細かい実装テクニックについて、新しく学んだことも多かったです。

2月上旬

先月活用したdeviseのユーザプロフィールを変更できるように実装しました。

routes.rb

  devise_for :users, controllers: {
    omniauth_callbacks: 'users/omniauth_callbacks',
    registrations: 'users/registrations'
  }

先月の課題にもあったユーザ登録の際に、ルーティングの設定でdevise_forの中にregistrations: 'users/registrations'を手動で設定した為、プロフィール登録だけでなく変更の際にも、deviseのビューファイル内のregistrations/editを使用して変更画面を呼び出す必要があります。

当初、後から追加したユーザコントローラ上でupdateのアクションを使いするものかと思っていましたが、よくよく考えたらユーザ登録の時点でdeviseを使用していたので、deviseに保存されたユーザデータを呼び出さなければ、更新できないなと納得しました。

続いて、ポストした文章の投稿時間がデフォルトの世界標準時のままだった為、日本標準時に変更しました。

始めに、configのapplication.rbに日本時間のタイムゾーン設定をします。

application.rb

config.time_zone = 'Asia/Tokyo'

続いて、同じくconfigのintializersディレクトリにtime_formats.rbというファイルを作り、その中に日本時間の表記設定をします。

time_formats.rb

Time::DATE_FORMATS[:datetime_jp] = '%Y年 %m月 %d日 %H時 %M分'

これで投稿時間が9時間前の時間ではなく日本の現在時刻が表示されるようになりました。

2月中旬

ユーザ同士のフォロー機能実装の為、同一のテーブル同士(自己結合)となる多対多のアソシエーションを学びました。

SNSアプリのように登録ユーザが他のユーザをフォローする際、お互いにユーザが他の沢山のユーザをフォローできます。従って多対多の関係になるので、中間テーブル(今回はフォローテーブル)が必要になり、外部キー制約を設定したユーザIDカラムを参照して、お互いのフォロー関係を管理します。

中間テーブルは親モデルであるユーザテーブルの所属になる為、belongs_toで連携します。

ただ、単にfollower_user, followed_userと記述してしまうと、存在しないfollower_user, followed_userテーブルを参照しに行ってしまいますので、下記のようにclass_name: 'User'と記述し、参照先にユーザモデルを指定します。

follow.rb

class Follow < ApplicationRecord
  belongs_to :follower_user, class_name: 'User'
  belongs_to :followed_user, class_name: 'User'
end

親モデルのユーザモデルファイルでは、フォローするユーザ側から見たデータ取得メソッドと、フォローされるユーザ側から見たデータ取得メソッドを設定することが出来ます。
これによってuser.followingsもしくはuser.followersでフォローしたユーザの人数・フォローされたユーザの人数の取得が簡単になります。

フォローユーザーから見た中間テーブル(followテーブル)を「follows」とする。「入り口はFKの'follower_user_id'」
followモデルに「belongs to :follower_user」と定義し、中間テーブルのfollowed_user_idカラムを「出口」として、フォローユーザのフォロー人数を集計する事を「followings」と定義(例:user.followings)
フォロワーユーザから見た中間テーブルを「passive_follows」とする。「入り口はFKの'followed_user_id'」
followモデルに「belongs to :followed_user」と定義し、中間テーブルのfollower_user_idカラムを「出口」として、フォロワーユーザのフォロワー人数を集計する事を「followers」と定義(例:user.followers)

user.rb

# フォローユーザからの視点
  
has_many :follows, inverse_of: :follower_user, class_name: 'Follow', foreign_key: 'follower_user_id', dependent: :destroy
has_many :followings, through: :follows, source: :followed_user
  
# フォロワーユーザからの視点
  
has_many :passive_follows, inverse_of: :followed_user, class_name: 'Follow', foreign_key: 'followed_user_id', dependent: :destroy
has_many :followers, through: :passive_follows, source: :follower_user

実際にWebアプリ上でもログインユーザのフォロー人数・フォロワー人数の取得を簡単に行うことが出来ました。

続いて、ツイート機能・いいね機能・コメント機能・リツイート機能を実装しました。
  • これらの機能については、始めに実装したツイートテーブルが親となり、一つのツイートには沢山のいいね・コメント・リツイートが付くことになる為、親子の関係になります。

  • ツイートにはツイート本文とツイートしたユーザIDが外部キーとして埋め込まれる形になり、アソシエーションによってツイートのレコードデータからユーザの特定が可能になります。

  • また、ツイートの子モデルではツイートIDと、いいね・コメント・リツイートしたユーザのIDが、外部キーとして埋め込まれることになります。
    ツイートレコードと同じように、各レコードデータからユーザやツイートの特定が可能になります。

実際に実装する際も、ツイート機能の実装が完了した後は、同じようなコードで実装がトントンと進みました。
おかげで、各機能ごとにメンターさんにレビューをレビューを頂く必要があった為連日、プルリクエスト申請の日々になりました。日々のレビューありがとうございます。

2月下旬

ユーザフォロー機能 ・ブックマーク機能の実装と、メッセージ機能実装を行いました。
始めに、先に実装してあったユーザテーブルとフォローテーブルのアソシエーションを活用し、ユーザ同士のフォロー機能を実装しました。

また、ツイートのブックマーク機能に関しても、先にいいねやリツイートの実装を学んでいた為、すんなりと実装することが出来ました。

2月最後の課題となったメッセージ機能(いわゆるDM機能)では、当初想定していた実装とはかけ離れた考え方も応用的で、複雑な実装を迫られることになりました。

当初、私はコメント機能と同じように、外部キーにユーザIDを持ったメッセージテーブルを作成し、ユーザ同士のアソシエーションを設定すれば良いと思っていました。

しかし実装を進めるうちに、作成したメッセージがどのユーザのものなのか?メッセージ一覧画面でログインユーザと、メッセージを送りつけたユーザを、どのように紐づけるのかわからなくなってしまいました。(今から考えれば情報が少なすぎるので当然と言えば当然ですが…)

必要に迫られてWebで検索した所、機能を実装するにあたり、

  • メッセージをやり取りするお互いのユーザは一つの部屋に紐づけられること
  • その部屋は沢山のユーザ同士がメッセージをやり取りするたびに作成される為、ユーザとメッセージ部屋は多対多の関係になること
  • ユーザとメッセージ部屋の紐付けを管理する為、中間テーブルとしてエントリーテーブルが必要であること。
  • その中で当然ながらメッセージのやり取りが行われるため、メッセージテーブルを中間テーブルとして作成し、メッセージにも外部キーとしてユーザIDと部屋のルームIDを紐づけること。

が必要であることがわかりました。

また、今回の要件ではメッセージ部屋の一覧画面と、メッセージ詳細画面を同一画面内で表示させるよう指示がありました。

今まで作ってきた一覧画面から一つのレコードデータを選択し、詳細画面に遷移させるルーティングとは別の動作になる為、一覧画面に遷移する際、どのようにしてパラメータIDを送信するか悩みました。

試行錯誤の末の解決策としては、メッセージ部屋のindexビューに遷移する際、httpのパラメータとしてuser.idを付与することで同一index画面内でもIDパラメータを渡すことが出来ました。

rooms_controller

# 通常indexビューに遷移するヘルパーメソッドはrooms_path → rooms_path(user.id)に変更。  
# その後、model#indexアクションにて、送信されて来たidパラメータが存在する場合のアクションを実装する。  
# .presenceメソッドでパラメータIDが付与されているか判断。if params[:id].present?と同義
Room.find_by(user_id: params[:id].presence)
  

今回に限らず要件に沿った動作を実現する為に、Railsは痒い所に手が届く柔軟な設計と幅広いテクニックがあり、奥が深いことを再確認しました!