EY-Office ブログ

RailsプログラマーのためのPrisma入門(2)

先週のRailsプログラマーのためのPrisma入門(1)の続きです。
今回は、Seed(初期データ)と FindMany(データ取得API)に付いて書きます。

prisma_for_rails 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"]
}

そして、findManyrelationLoadStrategyオプションを指定します。

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に書かれています。

- about -

EY-Office代表取締役
・プログラマー
吉田裕美の
開発者向けブログ