※画像が見づらくてすみません...アップして頂けると幸いです...
※updateアクションなどに保存失敗が考慮されていない部分ありますがまだ実装途中なのでご容赦ください...
開発環境
Ruby: v2.5.7
Rails: v5.2.4.4
PostgreSQL: psql (PostgreSQL) 13.0 (Debian 13.0-1.pgdg100+1)
概要
「ユーザーが所属するチーム名を取得して表示する」ことを目的に以下のモデル関連付けを行いました。
そして、中間テーブルを介した際に欲しい値を取得出来ずにハマりました。
- Userモデル
- Teamモデル
- TeamMembersモデル (両モデル間を繋ぐ中間テーブル)
Userモデル側では
through を使って、Teamモデルへアクセス出来るように関連付けしています。
ハマったこと
中間テーブルを介してteamテーブルから取得した場合とTeamモデルから取得した場合で結果が異なりハマりました。
UserController でデバッグし、2種類のパターンから取得したデータを確認したところ
コンソール画面の最後で、チーム名を取得したいと思いコマンドを実行していますがTeamモデルから取得した場合は、チームが選択出来ているのに対して中間テーブルを介した場合は、先頭に付いている#<Team:0x00...
のTeam
が取得されてしまいチーム名が取得出来なく、その原因が分からずハマりました。
結論
今回、中間テーブルを作り多対多のアソシエーションを組んでいるのでユーザーは複数のチームに所属出来ることになっています。
そのため、中間テーブルを介して取得してきたデータは複数になるので配列になっており
(よく見たら[]
で囲まれてました...)
チーム情報を取得するにはインデックスで個別に指定してあげる必要がありました。
なので、@user.join_teams
を@user.join_teams[0]
と変更することでチーム情報を取得でき@user.join_teams[0].name
とすることでチーム名を取得することが出来ました。
要因が分かってみると基礎中の基礎でした...
反省します!!
ただ、今回の件で
- コンソール上のSQL文からもヒントを得られるということ
- クラスを調べることが原因を調べる上で有効な手段であること
を、知ることが出来ました。
(色々と調べても分からず、現役エンジニアである友人からこの手法で答えを考えてみなと教えてもらいました。)
SQL文からヒントを得る
今回のコンソール上のSQL文ですが、
=> Team Load (2.4ms) SELECT "teams".* FROM "teams" INNER JOIN "team_members" ON "teams"."id" = "team_members"."team_id" WHERE "team_members"."user_id" = $1
と、表示されていました。
このメッセージを調べて解釈してみたのですが
teamテーブルから全てのデータを取得しteam_membersテーブルにteamテーブルのidを対応させる形で内部結合したのちに、team_membersテーブルのuser_idカラムを条件にして取得する
ということでした。(間違ってましたら是非、教えて頂きたいです...)
2つのテーブルを内部結合して、複数のデータがヒットする可能性があるというヒントがSQL文にありました。
内部結合や外部結合に関しては
https://zenn.dev/naoki_mochizuki/articles/60603b2cdc273cd51c59
こちらの方の記事が分かりやすく、参考にさせて頂きました。
クラスから原因を調べる
コンソール上で、クラスを見てみたところ
pry(#<UsersController>)> @user.join_teams.class => Team::ActiveRecord_Associations_CollectionProxy
と、表示されました。
このクラスなんですが、RailsのActiveRecordモジュールのクラスの1つで、has_manyで関連をもたせたときにその関連で取得したデータがインスタンスに入っているとのこと。
ここから、複数データがインスタンス内に入っていると知ることが出来たかもしれません。
最後に
「そもそも、多対多の関係なんだから複数のデータが取得されて当然だろう」と言われてしまう内容なのですが
中間テーブルの関連付けデータを1つしか作っていなかったことで、自分で自分を惑わしてしまいハマってしまいました。
基礎、基本が重要であることに加えて今回の件で言えばクラスとSQL文から原因を掴むヒントを得られると知れたので自分としてはまた成長出来たかなと思っています。
ActiveRecordのおかげでデータベース言語を打つ場面が日頃少ないのでSQLについてはあまり触れていませんでしたが、SQLの知識も活用することで今回のように更に広い視野で原因を掴めると気付けました。