先週のRailsプログラマーのためのPrisma入門(1)の続きです。
今回は、Seed(初期データ)と FindMany(データ取得API)に付いて書きます。
Bing Image Creatorが生成した画像を使っています
Seed
(開発環境の)初期データを設定できるSeed機能は、地味ですがありがたい機能ですね。Primsaにも当然Seeding機能があります。
package.json
ファイルのprisma.seed
に定義されているコマンドを実行する機能です。
今回は、Seeding your database with TypeScript or JavaScriptを参考に作りました。
設定
package.json
ファイルに以下を追加し prisma/seed.ts
を実行するようにしました。ここにpsql
コマンド等を書いて起動することもできるのでSQL文でSeed機能を実現する事もできます。
"prisma": {
"seed": "ts-node prisma/seed.ts"
},
コード
Seedデータを読み込み、prismaのcreateMany APIを使いデータベースにデータを作成しています。
再実行できるようにPostgreSQLのTRUNCATE TABLE
文でテーブルの全デーを削除しIDの連番をリセットしています。
prisma/seed.ts
import { PrismaClient } from '@prisma/client';
import { CountriesData } from './seeds/countries';
import { CatsData } from './seeds/cats';
const prisma = new PrismaClient();
(async () => {
try {
await prisma.$queryRaw`TRUNCATE TABLE countries, cats RESTART IDENTITY;`;
const countries = await prisma.country.createMany({
data: CountriesData
});
const cats = await prisma.cat.createMany({
data: CatsData
});
console.log('Seed: ', countries.count, cats.count);
} catch (e) {
console.error(e)
process.exit(1)
} finally {
await prisma.$disconnect();
}
})();
prisma/seeds/cats.ts
export const CatsData = [
{
"name": 'Abyssinian',
"referenceImageId": '0XYvRd7oD',
"temperament": 'Active, Energetic, Independent, Intelligent, Gentle',
"description": 'The Abyssinian is easy to care for, and a joy to have in your home. They’re affectionate cats and love both people and other animals.',
"countryId": 818
},
{
"name": 'Aegean',
"referenceImageId": 'ozEvzdVM-',
"temperament": 'Affectionate, Social, Intelligent, Playful, Active',
"description": 'Native to the Greek islands known as the Cyclades in the Aegean Sea, these are natural cats, meaning they developed without humans getting involved in their breeding. As a breed, Aegean Cats are rare, although they are numerous on their home islands. They are generally friendly toward people and can be excellent cats for families with children.',
"countryId": 300
},
・・・省略・・・
prisma/seeds/countries.ts
export const CountriesData = [
{
"id": 4,
"countryCode": "AF",
"name": "Afghanistan"
},
{
"id": 8,
"countryCode": "AL",
"name": "Albania"
},
・・・省略・・・
FindMany
さて、データベースからデータを取得するAPIに付いて解説します、今回はfindManyです。
Ruby on Railsでは todos = Todo.where(done: true).order(:created_at)
のようなカッコ良い書き方でデータベースからデータを取得できました。
しかし、PrismaにはこんなAPIはありません。同じ事は以下のように書きます、
const todos = await prisma.todo.findMany({
where: {done: true},
orderBy: {created_at: 'asc'},
});
Railsバージョン2のall()
メソッドみたいですね。Rails大好きな方には残念かもしてませんが・・・
さて今回のサンプルコードのデータベースは、猫のテーブルcatsと国のテーブルcountriesがあります。
model Cat {
id Int @id @default(autoincrement())
name String @db.VarChar(255)
countryId Int? @map("country_id")
referenceImageId String? @db.VarChar(255) @map("reference_image_id")
temperament String? @db.VarChar(255)
description String? @db.Text
country Country? @relation(fields: [countryId], references: [id])
@@map("cats")
}
model Country {
id Int @id @default(autoincrement())
name String @db.VarChar(255)
countryCode String @db.VarChar(2) @map("country_code")
cats Cat[]
@@map("countries")
}
猫の名前と生まれた国の名前を表示するコードは以下のようになります
const cats = await prisma.cat.findMany({
orderBy: {id: 'asc'},
include: { country: true }
});
cats.forEach(cat => {
console.log("name:", cat.name, ", country:", cat.country?.name ?? "--");
});
実行されるSQL(SQLログ)
Railsの開発環境ではデフォルトでコンソールに、実行されるSQLが表示されます。PrismaでSQLを表示するには以下のように設定(コード)を追加する必要があります。
const prisma = new PrismaClient({
log: [
{
emit: "event",
level: "query",
},
]
});
prisma.$on("query", async (e) => {
console.log(`${e.timestamp.toISOString()} SQL: ${e.query} \nParams: ${e.params}`)
});
関連テーブルのデータ取得
上の猫データの猫の名前と生まれた国の名前を表示するコードでは以下のようなSQLが実行されています。
2024-06-24T05:53:53.059Z SQL: SELECT "public"."cats"."id", "public"."cats"."name",
"public"."cats"."country_id", "public"."cats"."reference_image_id",
"public"."cats"."temperament", "public"."cats"."description"
FROM "public"."cats"
WHERE 1=1 ORDER BY "public"."cats"."id" ASC OFFSET $1
Params: [0]
2024-06-24T05:53:53.061Z SQL: SELECT "public"."countries"."id",
"public"."countries"."name", "public"."countries"."country_code"
FROM "public"."countries" WHERE "public"."countries"."id" IN
($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18) OFFSET $19
Params: [818,300,840,784,36,250,826,104,124,196,643,156,392,764,578,364,706,792,0]
Joinではなく、cats, countriesテーブルを1回づづSELECTしています。Railsでもincludes()
メソッドを使うと、このようなSQLが実行されますよね。
さて、PrismaドキュメントのRelation queriesによるとPreview機能としてJOINで取得できるようです。
まず、設定schema.prisma
にpreviewFeaturesを追加します。
generator client {
provider = "prisma-client-js"
previewFeatures = ["relationJoins"]
}
そして、findMany
に relationLoadStrategy
オプションを指定します。
const cats = await prisma.cat.findMany({
relationLoadStrategy: 'join',
orderBy: {id: 'asc'},
include: { country: true }
});
このコードを実行すると、ちゃんとJOINが使われていますね。
2024-06-24T05:53:22.808Z SQL: SELECT "t1"."id", "t1"."name", "t1"."country_id" AS
"countryId", "t1"."reference_image_id" AS "referenceImageId", "t1"."temperament",
"t1"."description", "Cat_country"."__prisma_data__" AS "country"
FROM "public"."cats" AS "t1" LEFT JOIN LATERAL (SELECT JSONB_BUILD_OBJECT('id',
"t2"."id", 'name', "t2"."name", 'countryCode', "t2"."country_code") AS
"__prisma_data__" FROM "public"."countries" AS "t2" WHERE "t1"."country_id" =
"t2"."id" LIMIT $1) AS "Cat_country" ON true ORDER BY "t1"."id" ASC
Params: [1]
まとめ
現状では20年の歴史があるRuby on Railsと、4年くらいのPrismaではAPIの洗練度には違いがありますね。
また現時点では正式にJoinがサポートされてないなど、まだ発展中の感もありますが大部分のアプリではじゅうぶんな機能を持っていると思います。現在開発中の機能はPreview featuresに書かれています。