はじめに
この記事では、コードレビューを受けた際に生まれた
- この2つのコードって結局同じ結果じゃないの?
- どうやって使い分ければいいの?
という疑問を整理しながら、「親→子」・「子→親」関係のアクセス方法についてアウトプットしていきます。
例
以下が今回のモデル構成です。
ユーザー、投稿、いいねの3つを用意しました。
class User(models.Model):
name = models.CharField(max_length=100)
class Post(models.Model):
user = models.ForeignKey(User, related_name="posts", on_delete=models.CASCADE)
content = models.TextField()
class Like(models.Model):
user = models.ForeignKey(User, related_name="likes", on_delete=models.CASCADE)
post = models.ForeignKey(Post, related_name="likes", on_delete=models.CASCADE)
どっちも同じ結果になるコード
今から2つのコードを比較していきます。
以下のコードの目的は
プロフィールページにおいてユーザーがいいねした投稿一覧を取得することです。
① 子→親 (Like
起点)
user = User.objects.get(id=1)
likes = Like.objects.filter(user=user).select_related("post")
posts = [like.post for like in likes]
実行順序は以下です。
-
users
テーブルから IDが「1」のユーザーを取得 - このユーザーが押した「いいね(
Like
)」をすべて取り出す - 各
Like
に紐づく投稿(Post
)も、DBから一緒に取得(select_related("post")
) - for文で、各
Like
に対して投稿を取得してリストにする
② 親→子 (User
起点)
user = User.objects.get(id=1)
likes = user.likes.select_related("post")
posts = [like.post for like in likes]
実行順序は以下です。
-
users
テーブルから IDが「1」のユーザーを取得 - このユーザーが持つ「いいね(
user.likes
)」をすべて取り出す - 各
Like
に紐づく投稿(Post
)を、DBから一緒に取得(select_related("post")
) - for文で、各
Like
から投稿を取り出してリスト化
細かく説明しているので2,3はあえて分けています
どのようなケースで利用するか
同じ結果を得られる2つのコードでも、「何を起点にしてデータを取得するか」で使い分けができます。
子→親
Like
, Post
, Comment
など「アクション的なデータ」からユーザーや投稿にアクセスしたいときです。
具体例
- 通知一覧:誰がどの投稿にいいね・コメントしたか
- タイムライン:投稿+投稿者情報
- リアクション履歴:最近のいいね履歴など
# 投稿+投稿ユーザー情報をタイムラインで取得
def get_post_list():
return Post.objects.select_related('user')
posts = get_post_list()
for post in posts:
print(post.message) # 投稿本文
print(post.user.username) # 投稿したユーザー名
print(post.user.profile_img) # 投稿したユーザーのプロフィール画像
親→子
ユーザーを起点に、関連する投稿・いいね・コメントなどを取り出したいときです。
具体例
- プロフィールページ
- 投稿一覧
- いいね一覧
user = User.objects.get(id=1)
# 「ユーザーが」.「いいねした」.「投稿一覧」
user.likes.select_related("post")
posts = [like.post for like in likes]
今回のケースでは
今回のように「特定のユーザーがいいねした投稿一覧を取得する」という目的では
親→子(User起点) でデータを取得するのがベターです。
理由は、そのユーザーに紐づいたデータ(いいね)を扱っているため、user.likes.select_related("post")
のように記述することで、可読性が高くなり、将来的にコードの保守や拡張もしやすくなるからです。
最後に
今回のアウトプットでは
以下の3点が理解できました。
- 「親→子」「子→親」は、目的や視点に応じて使い分ける
- ユーザーが中心なら
user.likes.select_related(...)
のように親→子が自然 - 通知や全体表示など、アクションを起点にするなら子→親も有効
もし、私のアウトプットで間違っている部分や
もっと別の書き方がいいんじゃないかと思う方は
是非コメントをお願い致します。
最後まで読んでいただきありがとうございました。