EY-Office ブログ

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

少し前のブログ(これこれ)で書いたように「Prismaを、もう少し学ばなくては」を実践しています。

私は長年Ruby on Railsを使ってきたので有名なORMであるActive Recordに付いては良く知ってます。 したがってPrismaは簡単にわかると思っていましたが、調べてみると大きく異なるORMのようです。

今回は、マイグレーション(migration) に付いて書きます。

prisma_for_rails Bing Image Creatorが生成した画像を使っています

まずはインストールと設定の手順

Prismaを使う時には、毎度インストールや設定を調べているのでまとめておきます。

インストール

本番環境で使われるのはPrismaクライアント・ライブラリー@prisma/clientのみで、マイグレーション等のツールであるprisma は開発環境のみにインストールします。

$ npm install @prisma/client
$ npm install --save-dev prisma

設定手順

  1. npx prisma init でPrisma用ディレクトリーや(空の)スキーマ・ファイルを生成
  2. スキーマ・ファイル prisma/schema.prisma を設定
    • 今回はPostgreSQLを使います
    • shadowDatabaseUrlに付いては少し前のブログに書きました
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider          = "postgresql"
  url               = env("DATABASE_URL")
  shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
}
  1. 必要ならPostgreSQLのアカウントやデータベースを作成
  2. .envファイルにPostgreSQL接続URLを記述
DATABASE_URL="postgres://postgres:postgres@localhost:5432/postgres"
SHADOW_DATABASE_URL="postgres://postgres:postgres@localhost:5432/postgres_shadow"
  1. npx prisma generateでRDB接続用ライブラリーを作成
    • ./node_modules/.prisma/client/にライブラリーや設定ファイルが作成されます

Prismaのマイグレーション

今回はサンプルのテーブルを作成し、修正していった手順を書きます。データは以前も使ったTheCatAPIWorld countriesを使っています。

まずRailsとの違いをあげておきます、

  • Railsではテーブル作成(追加)はモデル作成のジェネレーターを使って作りますが、Prismaではスキーマ・ファイル prisma/schema.prismaで行います
  • Railsではテーブル変更はマイグレーション・ジェネレーターで変更用マイグレーションファイルを作りますが、Prismaではスキーマ・ファイル prisma/schema.prismaを直接変更します
    • PrismaではマイグレーションコマンドがRDBに依存したテーブル作成・変更用SQLファイルを生成します
    • ある意味で、マイグレーションの履歴はGitにしか残りません
  • Railsのマイグレーションファイルは独自DSL(Rubyメソッド)ですが、Prismaもスキーマ・ファイル prisma/schema.prismaは独自の文法です
  • Railsでは開発サイクルが始まってからのsqlite3からMySQLへの変更等したりできますが、Prismaではできません(正確にはマイグレーション用SQLファイルが使えなくなってしまいます)

マイグレーションの公式ドキュメントはPrisma Migrate - Getting startedPrisma Migrate - Overview … に書かれています、日本語の記事も多数あります。

1. 始め

猫(cats)のテーブルを作ります。model Cat {...}からテーブルが作成されます。

  • prisma/schema.prisma
generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider          = "postgresql"
  url               = env("DATABASE_URL")
  shadowDatabaseUrl = env("SHADOW_DATABASE_URL")
}

model Cat {
  id                 Int     @id @default(autoincrement())
  name               String  @db.VarChar(255)
  country_code       String  @db.VarChar(2)
  reference_image_id String  @db.VarChar(255)
  temperament        String  @db.VarChar(255)
  description        String  @db.Text
}

説明

  • @id 主キー
  • @default(autoincrement()) デフォルト値の定義、autoincrement()で連番が生成されます
  • @db.VarChar() 文字型の長さを指定しています
    • この指定がない場合のStringのデフォルトはRDB毎に決まっています →これ
  • @db.Text 制限無し可変長文字列

これをnpx prisma migrate devコマンドでマイグレーションすると以下のようなSQLが生成され、Catというテーブルが作成されました。さらに主キー用にCat_id_seqも生成されました。

CREATE TABLE "Cat" (
    "id" SERIAL NOT NULL,
    "name" VARCHAR(255) NOT NULL,
    "country_code" VARCHAR(2) NOT NULL,
    "reference_image_id" VARCHAR(255) NOT NULL,
    "temperament" VARCHAR(255) NOT NULL,
    "description" TEXT NOT NULL,
    CONSTRAINT "Cat_pkey" PRIMARY KEY ("id")
);

2. NULLカラム

1の定義では全てのカラムがNOT NULLになってしまいます。NULLを許すカラム定義は以下のように定義します。(TypeScriptのオブジェクトではプロパティー名の後ろに?を付けますが、Prismaでは型の後ろに?を付けます)

・・・省略・・・

model Cat {
  id                 Int     @id @default(autoincrement())
  name               String  @db.VarChar(255)
  country_code       String? @db.VarChar(2)
  reference_image_id String? @db.VarChar(255)
  temperament        String? @db.VarChar(255)
  description        String? @db.Text
}

これをマイグレーションすると以下のようなSQLが生成され実行されました。

ALTER TABLE "Cat"
  ALTER COLUMN "country_code" DROP NOT NULL,
  ALTER COLUMN "reference_image_id" DROP NOT NULL,
  ALTER COLUMN "temperament" DROP NOT NULL,
  ALTER COLUMN "description" DROP NOT NULL;

3. テーブル名、カラム名の変更

Catテーブルから取得したデータのオブジェクトのプロパティー名はJavaScriptのルール通りキャメルケース(例 country_codecountryCode)に変更します、ただしテーブルのカラム名はそのままにします。またテーブル名はRailsに合わせ小文字・複数形でcatsに変更しました。

  • prisma/schema.prisma
・・・省略・・・

model Cat {
  id                 Int     @id @default(autoincrement())
  name               String  @db.VarChar(255)
  countryCode        String? @db.VarChar(2) @map("country_code")
  referenceImageId   String? @db.VarChar(255) @map("reference_image_id")
  temperament        String? @db.VarChar(255)
  description        String? @db.Text

  @@map("cats")
}

説明

  • @map("country_code") モデルと違うカラム名を付けます
  • @@map("cats") モデル名と違うテーブル名を付けます

これをマイグレーションすると以下のようなSQLが生成され実行されました。テーブルを作り直してますね!
このような変更の場合は Are you sure you want to create and apply this migration? のような確認が表示されます。

DROP TABLE "Cat";
CREATE TABLE "cats" (
    "id" SERIAL NOT NULL,
    "name" VARCHAR(255) NOT NULL,
    "country_code" VARCHAR(2) NOT NULL,
    "reference_image_id" VARCHAR(255) NOT NULL,
    "temperament" VARCHAR(255),
    "description" TEXT,
    CONSTRAINT "cats_pkey" PRIMARY KEY ("id")
);

4. テーブル追加

国名コード(例、日本はJA)のテーブルを追加しました。

  • prisma/schema.prisma
・・・省略・・・

model Cat {
  id                 Int     @id @default(autoincrement())
  name               String  @db.VarChar(255)
  countryCode        String? @db.VarChar(2) @map("country_code")
  referenceImageId   String? @db.VarChar(255) @map("reference_image_id")
  temperament        String? @db.VarChar(255)
  description        String? @db.Text

  @@map("cats")
}

model Country {
  id                 Int      @id @default(autoincrement())
  name               String   @db.VarChar(255)
  countryCode        String   @db.VarChar(2) @map("country_code")

  @@map("countries")
}

とくに説明項目はありません、これをマイグレーションすると以下のようなSQLが生成され実行されました。

CREATE TABLE "countries" (
    "id" SERIAL NOT NULL,
    "name" VARCHAR(255) NOT NULL,
    "country_code" VARCHAR(2) NOT NULL,
    CONSTRAINT "countries_pkey" PRIMARY KEY ("id")
);

5. catsテーブルとcountriesテーブルを関連させる

  • prisma/schema.prisma
・・・省略・・・

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")
}
  • @relation(fields: [countryId], references: [id]) はcatsテーブルはcountry_idカラムを外部キーとして、countriesテーブルのidカラムに関連します
  • catsテーブルのcountry_codeカラムは削除しました
  • Catモデルのオブジェクトは、countryプロパティーで対応するCounryモデルをアクセスできます
  • また、Countryモデルのオブジェクトは、catsプロパティーで対応するCatモデルをアクセスできます

これをマイグレーションすると以下のようなSQLが生成され実行されました。

ALTER TABLE "cats"
  DROP COLUMN "country_code",
  ADD COLUMN  "country_id" INTEGER;

ALTER TABLE "cats" ADD CONSTRAINT "cats_country_id_fkey" FOREIGN KEY ("country_id") 
  REFERENCES "countries"("id") ON DELETE SET NULL ON UPDATE CASCADE;

Prismaのマイグレーションのまとめ

  • Prismaではスキーマ・ファイルを直接変更しながらマイグレーションを行います。
    • したがってGit等で履歴管理しないと過去の状態が判らなくなります
    • もちろん、マイグレーション毎にSQL文が生成されるので手間をかければ判らなくはないです
  • ただしALTER TABLE SQL文などのテーブルをメンテナンスするSQL文の知識が無くても、テーブルの変更等が簡単に行えます
    • (RailsでもALTER TABLE SQL文の詳細は判らなくても変更できますが、ある程度はSQL文の知識が必要だと思います)
  • マイグレーションの内容によってはテーブル内のデータが消えてしまう事があります。その際には警告が表示されます。
  • 開発サイクルが始まってからのRDB変更(例、sqlite3からMySQLへの変更)は大変です
    • 過去に生成されたマイグレーション用SQL文は使えなくなります

PrismaのマイグレーションはRuby on Railsとは大きく違います。プロジェクトチーム内にPrisma経験者がいない場合は、サンプル・プロジェクト等でマイグレーションを体験する事をお勧めします。

- about -

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