【Prisma】followerとfollowee、逆だったかもしれねェ...
記事の概要
SNSアプリのDBスキーマをPrismaで定義するときに、フォロワーと被フォロワーの関係を表現しようとして直感でスキーマを書いたら逆になってしまったので、教訓として対策を含めて記事に残すことにしました。
前提知識
Prismaで1対多の関係を定義したいときは、以下のように書きます。
model User {
id Int @id @default(autoincrement())
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
author User @relation(fields: [authorId], references: [id])
authorId Int
}
これは以下のSQLに対応しています。
CREATE TABLE "User" (
id SERIAL PRIMARY KEY
);
CREATE TABLE "Post" (
id SERIAL PRIMARY KEY,
"authorId" integer NOT NULL,
FOREIGN KEY ("authorId") REFERENCES "User"(id)
);
ポイントは、posts
はSQLには現れないという点です。Prismaの記法として、"1"側のモデルに"多"のモデルの配列を定義するルールになっています。
最初にやったこと
フォローと非フォローの関係を表現するFollowing
というモデルを作成しました。 User
への参照を定義した後、Prismaのルールに従ってUser
モデルの中にFollowing[]
型の変数を定義します。同じモデルへの参照が2つあるので、区別するために名前をつけておきます。(@relation("follower")
や@relation("followee")
)
ここで思考停止した私はFollowing.follower
に対応しているからUser.followers
, Following.followee
に対応しているからUser.followees
としました。
model Following {
followerId String
followeeId String
follower User @relation("follower", fields: [followerId], references: [id], onDelete: Cascade)
followee User @relation("followee", fields: [followeeId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@id([followerId, followeeId])
}
model User {
id String @id @default(cuid())
name String?
followers Following[] @relation("follower")
followees Following[] @relation("followee")
}
逆だったかもしれねェ...
上記のモデルで、「ユーザーID: AがユーザーID: Bをフォローしている」という関係を表すことを考えます。以下のようなデータを用意します。
User
id |
---|
A |
B |
Following
followerId | followeeId |
---|---|
A | B |
この時ユーザーID: Bのフォロワー配列を取得します。Prisma clientでは以下のように記述します。include
はSQLでいうところのjoinのようなものです。
prisma.user.findUnique({
where: {
id: "B"
},
include: {
followers: {
select: {
followerId: true
}
}
}
});
Aを要素とする配列['A']
が返ってくることを期待したのですが、結果は[]
でした。
なぜ逆だったのか
上記の処理では「followerIdが'B'であるようなFollowingのレコード配列」が返ってきます。それはつまりBのfolloweeの配列ということになります。
対策
つまり、正しくはFollowing.follower
とUser.followees
, Following.followee
とUser.followers
を対応づければよかったのです。
しかしそれは直感的でなく、そのソースを初めて見た人は「コーディングミスでは?」と思ってしまう可能性があります。そこで、relationの名前を工夫することにしました。
model Following {
followerId String
followeeId String
- follower User @relation("follower", fields: [followerId], references: [id], onDelete: Cascade)
- followee User @relation("followee", fields: [followeeId], references: [id], onDelete: Cascade)
+ follower User @relation("asFollower", fields: [followerId], references: [id], onDelete: Cascade)
+ followee User @relation("asFollowee", fields: [followeeId], references: [id], onDelete: Cascade)
createdAt DateTime @default(now())
@@id([followerId, followeeId])
}
model User {
id String @id @default(cuid())
name String?
- followers Following[] @relation("follower")
- followees Following[] @relation("followee")
+ followers Following[] @relation("asFollowee")
+ followees Following[] @relation("asFollower")
}
これなら、「自分を被フォロワーとしてのフォロワーリスト」、「自分をフォロワーとしての被フォロワーリスト」であることが読み取れます。
宣伝
先日、本を書きました!200円で販売中です(無料部分あり)。
T3 Stack(Next.js, tRPC, Prisma, NextAuth.js, TailindCSS)を使ったSNSのWebアプリケーション開発について、ユーザーとユースケースの定義から作ったWebアプリのデプロイまで、一通り説明しています。よかったら読んでみてください!
Discussion