2025/12/12

テクノロジー

スキーマ駆動開発で便利になりそうなRails To TypeSpecのnpmライブラリを作った

この記事の目次

    本記事は【Advent Calendar 2025】の10日目の記事です。

    はじめに

    ITD2-2-1のH・Tです。マイナビで内製開発しています。
    開発現場で作業効率が落ちるタスクがあったので、自分でライブラリを作ったのでその紹介をします。
    今後の内製開発でも大いに役立つかなと思ってます。

    なにをつくったか

    Tatsumaki - Rails to TypeSpec Generator

    npmにすでに公開しています。
    https://www.npmjs.com/package/@tyranno269/tatsumaki

    ライブラリが提供する価値

    RailsのDBスキーマからTypeSpecモデルを自動生成するnpmライブラリでスキーマ駆動開発を簡易化できます。

    開発モチベーション

    マイナビの開発現場では、Rails API + Next.js + zodによる型安全な開発が多いです、ただ以下の課題がありました。

    • 手動変換の煩雑さ: Rails schema.rb → TypeSpec → OpenAPI → zodの変換チェーンが手動
    • 型同期の遅延: Rails側の変更をフロントエンドに反映するのに時間がかかる
    • 型不整合のリスク: 手動変換によるランタイムエラーの発生


    OpenAPIからZod生成ではOrvalというライブラリを使用しています。
    これによってOpenAPIからシームレスに実装を進められます。一方でバックエンドのRailsからTypeSpecに書き出すのは少々手間をかけていました。ここが自分は煩雑だなと感じてました。これを解決しようと思ったのがライブラリ開発のモチベーションです。

    Tatsumakiによる解決

    # docsディレクトリで実行
    npx @tyranno269/tatsumaki
    # → rails.tsp自動生成 → TypeSpec → OpenAPI → orval → zod型定義

    機能

    Rails schema.rbから自動でTypeSpec生成
    Rails enum完全対応 - モデルファイルからenum定義を自動抽出・生成
    monorepo対応で柔軟なプロジェクト構造をサポート
    型安全性をRailsからフロントエンドまで一貫して確保
    Rails互換の単数形化 (company_branches​ → CompanyBranch​)
    参考: activesupport/lib/active_support/inflections.rb

    想定プロジェクト構造

    project/
    ├── backend/     # Rails API
    │   ├── app/models/  # Rails enum定義
    │   └── db/
    │       └── schema.rb
    ├── docs/        # TypeSpec (実行場所)
    │   └── rails.tsp (生成される)
    └── frontend/    # Next.js + zod

    Rails Enum

    RailsにはModel層にEnum値を定義します。書き方が多様にあります。Schema.rbではintegerですが、APIレスポンスとして返す値はStringのケースが多くあるので対応が必要でした。ただしEnumもi18nで翻訳ファイルを定義するとバックエンドから日本語化して返せますが、フロントエンドメンバーと相談した結果、翻訳はFE側で対応すると良いとの結論になり、純粋にmodelファイルの定義をTypeSpecに出力します。

    対応するenum形式

    class Company < ApplicationRecord
    # ハッシュ形式
    enum :company_status, { disabled: 0, enabled: 1, suspended: 9 }

    # 配列形式
    enum :status, [ :active, :archived ]

    # %i記法
    enum :priority, %i(low medium high)

    # キーワード引数
    enum priority: { low: 0, medium: 1, high: 2 }
    end
    class Book < ApplicationRecord
      enum :status, [ :draft, :published, :archived ]
    end

    生成されるTypeSpec

    namespace CompanyEnums {
      enum CompanyStatus {
        disabled,
        enabled,
        suspended,
      }

      enum Status {
        active,
        archived,
      }
    }

    namespace BookEnums {
      enum Status {
        draft,
        published,
        archived,
      }
    }

    model Company {
      id: int64;
      company_status: CompanyEnums.CompanyStatus; // default: "enabled"
      status: CompanyEnums.Status; // default: "active"
      created_at: utcDateTime;
      updated_at: utcDateTime;
    }

    model Book {
      id: int64;
      name: string;
      status: BookEnums.Status; // No naming conflict with CompanyEnums.Status
      created_at: utcDateTime;
      updated_at: utcDateTime;
    }

    enum機能の特徴

    名前空間による衝突回避 - CompanyEnums.Status​ vs BookEnums.Status
    schema.rbとの連携 - テーブル定義があるモデルのみ処理
    型置換 - int32​フィールドを適切なenum​型に変換
    全Rails enum構文対応 - ハッシュ、配列、%i記法、キーワード引数

    その他にも、a_matsudaさんのStateful_enumといったようにenum定義からブロックでイベントを記述するといったGemもあります。TypeSpecの出力ではブロック部分は回避する工夫もしました。

    開発フロー統合

    Tatsumakiの設計により、Rails schema.rb + enum → TypeSpec → OpenAPI → zod の完全自動化チェーンを実現し、型安全なフルスタック開発を支援可能になりました。

    インストール・使用方法

    # 実行(最新版が自動取得される)
    npx @tyranno269/tatsumaki

    # 既存ファイル上書き
    npx @tyranno269/tatsumaki --force

    # カスタム出力ファイル
    npx @tyranno269/tatsumaki --out models.tsp --force

    # 既存ファイルに追記
    npx @tyranno269/tatsumaki --append

    マイナビでの今後の活用方法

    TypeSpecディレクトリ構成次第にはなりますが様々なパターンの開発で応用できうるかとおもいます。

    パターン1

    project/
    ├── backend/     # Rails API
    │   ├── app/models/  # Rails enum定義
    │   └── db/
    │       └── schema.rb
    ├── docs/        # TypeSpec (実行場所)
    │   └── rails.tsp (生成される)
    └── frontend/    # Next.js + zod

    生成されるrails.tspにroutesになるオペレーション情報を記述していくパターン

    パターン2

    project/
    ├── backend/     # Rails API
    │   ├── app/models/  # Rails enum定義
    │   └── db/
    │       └── schema.rb
    ├── docs/        # TypeSpec (実行場所)
    │   └── rails.tsp (生成される)
    │   └── routes
    │       └── admin
    │           └── admin.tsp (ex:管理サイトの管理者一覧・詳細など)
    │       └── user
    │           └── notice.tsp (ex:ユーザーサイトのお知らせ一覧・詳細など)
    └── frontend/    # Next.js + zod

    生成されるrails.tspにroutesで定義したオペレーションをimportして組み込んでいくパターン

    パターン3

    project/
    ├── backend/     # Rails API
    │   ├── app/models/  # Rails enum定義
    │   └── db/
    │       └── schema.rb
    ├── docs/        # TypeSpec (実行場所)
    │   └── rails.tsp (生成される)
    │   └── main.tsp (出力の親となるファイル)
    │   └── models
    │       └── admin.tsp (rail.tspからnamespaceの該当部分をコピペで定義)
    │       └── notice.tsp (rail.tspからnamespaceの該当部分をコピペで定義)
    │   └── routes
    │       └── admin
    │           └── admin.tsp (ex:管理サイトの管理者一覧・詳細など)
    │       └── user
    │           └── notice.tsp (ex:ユーザーサイトのお知らせ一覧・詳細など)
    └── frontend/    # Next.js + zod

    生成されるrails.tspからコピペで抽出し利用していくパターン

    最後に

    実際のRailsのプロジェクトで使用される主要機能をカバーできたかなと思っています。よかったら利用してみてください。今回は自分の作業時間を減らしたいモチベーションで生み出したライブラリなので実際のユースケースで対応できていないこともあるかなと思います。要望があればGithub Issueに上げて頂けますと嬉しいです!!

    イベント告知

    12月23日にイベントを開催します!申し込みはこちらから▼

    https://mynaviit.connpass.com/event/376769

    ※本記事は2025年12月時点の情報です。

    著者:マイナビエンジニアブログ編集部