自動翻訳ビデオチャットを作ってみた

ざっくり流れ

  1. 以下のURL通り、firebaseの設定をした。 https://note.com/keyton61/n/n35c972d29961

  2. Cursorを開いて以下を満たすコードを書いてもらった。

音声からテキストへの変換は、SpeechRecognition APIを利用。

WebRTCのtrackイベントで相手のメディアストリームを受信できるので、それをAPIに渡す。

出力されたテキストを、Gemini APIAPI Key を露出させたくないので、Cloud functions 経由で呼び出し)で、翻訳。

翻訳結果を、speechSynthesis APIで読み上げ。

という感じで下のコードのような感じ。

FirebaseRTC/public/app.js at main · okadakk/FirebaseRTC · GitHub

感想

Cursorのcomposerが便利すぎてびびった。ほぼ一発で動いた。

とりあえずWebRTCについては動かしただけだから、もう少し調査したい。(専門で扱われてる方のTwitterで見たデモと違って、なんかもっさりしてた。)

保育園のシフト作成についてGeminiに質問しながら進めてみた備忘録

保育士の妻と遊びがてら、gemini に質問しながら作ってみた。

1. geminiにざっくり質問

質問

保育士シフト作成アプリの要件定義を、より詳細かつ網羅的に記述してください。考慮すべき点も追加してください。

回答

保育士シフト作成アプリの要件定義(詳細版)

1. 機能要件

職員管理: 職員の登録・編集・削除:氏名、生年月日、住所(任意)、電話番号、メールアドレス、資格(保育士、幼稚園教諭など、複数資格対応)、雇用形態、雇用期間、写真(任意)、備考欄、パスワード設定(セキュリティ考慮)

職員の属性管理:スキル(ピアノ、絵画、英語など、複数スキル対応、レベル設定)、経験年数、得意分野、苦手分野、希望休設定(事前登録、個別設定、繰り返し設定)、アレルギー情報、その他必要な情報

職員のシフト状況確認:個々の職員のシフト状況をカレンダー表示、一覧表示

職員の休暇管理:有給休暇、慶弔休暇、育児休暇、介護休暇などの申請、承認、管理機能。残日数の表示。

非常勤職員のシフト調整:非常勤職員の勤務時間、曜日、希望などを考慮したシフト作成機能。

シフト作成機能:

シフト作成期間指定:月単位、週単位、日単位でのシフト作成。

勤務時間帯設定:複数時間帯の設定、休憩時間の考慮、早番・遅番の設定...

2. 妻に聞きながら要件を絞る。

- 職員管理:
  - 職員の登録・編集・削除:氏名、経験年数、勤務可能時間(曜日毎)、担当クラス、好ましい時間希望(早番の方が遅番より好き)
  - 職員の休暇希望の登録・編集・削除

- 園児管理:
  - 園児の登録・編集・削除:氏名、クラス名、保育時間(曜日毎)

シフト作成機能:
  - シフト作成期間は月単位。
  - 勤務時間帯設定:複数時間帯の設定、休憩時間の考慮、早番・遅番の設定。
  - 職員配置:職員の経験年数、担当クラス、希望などを考慮した自動配置機能。手動配置も可能。
  - 人員配置基準設定:園児数に応じた時間毎の人員配置基準を設定し、それに基づいた自動提案。

3. gemini にデータベース設計を依頼

Prisma使おうと思うのでその旨も記載。

質問

保育士シフト作成アプリの要件を以下に列挙します。

この要件を実現するために必要な、データベース設計を提案してください。 シフト情報との関連性も考慮してください。

=========
2で整理した要件を記載。
=========

データ型は、Prismaがサポートする型を使用してください。 リレーションシップは適切に定義してください。 可能な限り、効率的で保守性の高いスキーマになるように設計してください。 出力は、Prisma Schema言語 (.prisma) で記述されたファイルの内容として出力してください。

回答

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql" // または MySQL, SQLite など。環境に合わせて変更してください。
  url      = env("DATABASE_URL")
}

model Staff {
  id                  Int      @id @default(autoincrement())
  name                String
  experienceYears     Int
  preferredShift      String? // 例: "early", "late", "neutral"
  assignedClasses     String? // クラス名、複数クラスの場合はコンマ区切りなど
  createdAt           DateTime @default(now())
  updatedAt           DateTime @updatedAt

  workAvailability    WorkAvailability[]
  vacationRequests    VacationRequest[]
  shifts              Shift[]
}

model Child {
  id            Int      @id @default(autoincrement())
  name          String
  className     String
  createdAt     DateTime @default(now())
  updatedAt     DateTime @updatedAt
  careTimes     CareTime[]
}


model WorkAvailability {
  id        Int      @id @default(autoincrement())
  staffId   Int      @unique
  staff     Staff    @relation(fields: [staffId], references: [id])
  dayOfWeek String   // 例: "Monday", "Tuesday" ...
  startTime Time?
  endTime   Time?
}

model VacationRequest {
  id          Int      @id @default(autoincrement())
  staffId     Int
  staff       Staff    @relation(fields: [staffId], references: [id])
  startDate   DateTime
  endDate     DateTime
  status      String   // 例: "pending", "approved", "rejected"
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}

model Shift {
  id              Int      @id @default(autoincrement())
  date            DateTime
  startTime       Time
  endTime         Time
  staffId         Int
  staff           Staff    @relation(fields: [staffId], references: [id])
  room            String?
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt
}


model CareTime {
  id          Int      @id @default(autoincrement())
  childId     Int
  child       Child    @relation(fields: [childId], references: [id])
  dayOfWeek   String
  startTime   Time
  endTime     Time
}

model StaffingStandard {
  id          Int      @id @default(autoincrement())
  numberOfChildren Int
  timeOfDay     String // 例: "morning", "afternoon"
  requiredStaff Int
}


説明:
Staff モデル: 保育士の情報。preferredShift は好ましいシフトを表し、assignedClasses は担当クラスを格納します。workAvailability は勤務可能時間を曜日ごとに保持する関連モデルです。

Child モデル: 園児の情報。careTimes は保育時間を曜日ごとに保持する関連モデルです。

WorkAvailability モデル: 職員の勤務可能時間を曜日ごとに記録します。Staff モデルと One-to-many の関係です。

VacationRequest モデル: 休暇申請の情報。Staff モデルと One-to-many の関係です。

Shift モデル: シフト情報。Staff モデルと One-to-many の関係です。room は保育室などを表します。

CareTime モデル: 園児の保育時間を曜日ごとに記録します。Child モデルと One-to-many の関係です。

StaffingStandard モデル: 園児数に応じた時間毎の人員配置基準を設定します。

4. gemini にデータベース設計の修正を依頼

質問(実際は何回かに分けて質問しました。)

Class モデルを別テーブルにしてください。その時に、Classはわかりづらいので、名前を変えてください。
また、StaffingStandardモデルの時間は、15分単位で表現してください。こちらも別テーブルで管理できるようにしてください。
さらに、Shiftに関して、今startTimeとendTimeで定義していますが、これを他のテーブルに抜き出して、例えばシフトA1、A2、B1、B2 のように名前をつけて再利用できるようにしたいです。

最終回答

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"  // 環境に合わせて変更してください
  url      = env("DATABASE_URL")
}

// 職員テーブル: 保育園や幼稚園で働く職員に関する情報を管理します。
model Staff {
  id                  String   @id @default(uuid()) @db.Uuid
  name                String
  experienceYears     Int
  preferredShift      String? // 例: "early", "late", "neutral"
  createdAt           DateTime @default(now())
  updatedAt           DateTime @updatedAt

  workAvailability    WorkAvailability[]
  vacationRequests    VacationRequest[]
  shifts              Shift[]
  assignedClassrooms  Classroom[] @relation("StaffClassroom")
}

// 園児テーブル: 保育園や幼稚園に通う園児に関する情報を管理します。
model Child {
  id              String   @id @default(uuid()) @db.Uuid
  name            String
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt
  careTimes       CareTime[]
  classroom       Classroom @relation(fields: [classroomId], references: [id])
  classroomId     String?   @db.Uuid
}

// 職員の勤務可能時間帯テーブル: 職員が勤務可能な曜日と時間帯を管理します。
model WorkAvailability {
  id        String   @id @default(uuid()) @db.Uuid
  staffId   String   @unique
  staff     Staff    @relation(fields: [staffId], references: [id])
  dayOfWeek String   // 例: "Monday", "Tuesday" ...
  startTime Time?
  endTime   Time?
}

// 職員の休暇申請テーブル: 職員の休暇申請に関する情報を管理します。
model VacationRequest {
  id          String   @id @default(uuid()) @db.Uuid
  staffId     String
  staff       Staff    @relation(fields: [staffId], references: [id])
  startDate   DateTime
  endDate     DateTime
  status      String   // 例: "pending", "approved", "rejected"
  createdAt   DateTime @default(now())
  updatedAt   DateTime @updatedAt
}

// シフトテーブル: 職員の勤務シフトに関する情報を管理します。
model Shift {
  id              String   @id @default(uuid()) @db.Uuid
  date            DateTime
  staffId         String
  staff           Staff    @relation(fields: [staffId], references: [id])
  room            String?
  shiftPatternId  String
  shiftPattern    ShiftPattern @relation(fields: [shiftPatternId], references: [id])
  createdAt       DateTime @default(now())
  updatedAt       DateTime @updatedAt
}

// 園児の保育時間テーブル: 園児の保育時間に関する情報を管理します。
model CareTime {
  id          String   @id @default(uuid()) @db.Uuid
  childId     String
  child       Child    @relation(fields: [childId], references: [id])
  dayOfWeek   String
  startTime   Time
  endTime     Time
}

// 教室テーブル: 教室に関する情報を管理します。
model Classroom {
  id          String   @id @default(uuid()) @db.Uuid
  name        String   @unique
  children    Child[]
  staff       Staff[]  @relation("StaffClassroom")
}

// 職員配置基準テーブル: 園児数に応じて必要な職員数を管理します。
model StaffingStandard {
  id                String   @id @default(uuid()) @db.Uuid
  numberOfChildren  Int
  timeOfDayId       String
  requiredStaff     Int
  timeOfDay         TimeOfDay @relation(fields: [timeOfDayId], references: [id])
}

// 時間帯テーブル: シフト作成で使用する時間帯を定義します。
model TimeOfDay {
  id          String   @id @default(uuid()) @db.Uuid
  startTime   Time
  endTime     Time
}

// シフトパターンテーブル: 勤務時間のパターンを定義し、シフト作成で再利用します。
model ShiftPattern {
  id          String   @id @default(uuid()) @db.Uuid
  name        String   @unique // 例: "A1", "A2", "B1"
  startTime   Time
  endTime     Time
  shifts      Shift[]
}

5. gemini にサンプルデータの作成を指示。

質問

このDB設計に合わせてサンプルデータを作成してください。
そして、あなたにシフトを作成の依頼をするために必要な入力形式を考え、それに合わせて出力してください。

回答

サンプルデータ

まずは、データベースの各テーブルに挿入するサンプルデータです。 UUIDはダミーデータとして生成しています。 実際の運用では、UUIDを生成する適切な方法を用いてください。

[
  // Staff
  {
    "id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
    "name": "山田 花子",
    "experienceYears": 5,
    "preferredShift": "early",
    "createdAt": "2024-01-01T12:00:00Z",
    "updatedAt": "2024-01-01T12:00:00Z"
  },
  {
    "id": "f0e9d8c7-b6a5-4321-9876-543210fedcba",
    "name": "鈴木一郎",
    "experienceYears": 2,
    "preferredShift": "late",
    "createdAt": "2024-01-01T12:00:00Z",
    "updatedAt": "2024-01-01T12:00:00Z"
  },
  // ... more Staff data ...
  // Child
  {
    "id": "11223344-5566-7788-9900-aabbccddeeff",
    "name": "田中太郎",
    "createdAt": "2024-01-01T12:00:00Z",
    "updatedAt": "2024-01-01T12:00:00Z",
    "classroomId": "cccddd-eee-fff-ggg-hhhiijjkklll"
  },
  // ... more Child data ...
  // Classroom
  {
    "id": "cccddd-eee-fff-ggg-hhhiijjkklll",
    "name": "ひよこ組"
  },
  // ... more Classroom data ...
  // ShiftPattern
  {
      "id": "pppppp-qqq-rrr-sss-tttuuvvvwwwwx",
      "name": "A1",
      "startTime": "08:00",
      "endTime": "17:00"
  },
  // ... more ShiftPattern data ...
  // ... other tables ...
]

このデータは、Staff、Child、Classroom、ShiftPatternテーブルのサンプルデータです。 WorkAvailability、VacationRequest、CareTime、Shift、StaffingStandard、TimeOfDay テーブルのデータは、必要に応じて作成してください。

シフト作成依頼のための入力形式と出力例

シフト作成を依頼するために必要な入力は、JSON形式で以下のように記述します。

{
  "date": "2024-02-01",  // シフト作成日
  "classroomIds": ["cccddd-eee-fff-ggg-hhhiijjkklll", "xxxxxxx-yyy-zzz-111-222222222222"], // 対象の教室のIDリスト
  "shiftPatternIds": ["pppppp-qqq-rrr-sss-tttuuvvvwwwwx"], // 使用するシフトパターンのIDリスト
  "staffIds": ["a1b2c3d4-e5f6-7890-1234-567890abcdef", "f0e9d8c7-b6a5-4321-9876-543210fedcba"],  // 使用可能な職員のIDリスト
  "staffingStandards": [ // 人員配置基準
    {
      "numberOfChildren": 10,
      "timeOfDayId": "time-id-1" // 午前中の時間帯ID
    },
    {
      "numberOfChildren": 15,
      "timeOfDayId": "time-id-2" // 午後の時間帯ID
    }
    // ... more staffingStandards
  ]
}
この入力に基づいて、シフト作成アプリはシフトを作成します。出力は、以下のJSON形式で返します。

{
  "shifts": [
    {
      "date": "2024-02-01",
      "startTime": "08:00",
      "endTime": "17:00",
      "staffId": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
      "classroomId": "cccddd-eee-fff-ggg-hhhiijjkklll",
      "shiftPatternId": "pppppp-qqq-rrr-sss-tttuuvvvwwwwx"
    },
    {
      "date": "2024-02-01",
      "startTime": "08:00",
      "endTime": "17:00",
      "staffId": "f0e9d8c7-b6a5-4321-9876-543210fedcba",
      "classroomId": "xxxxxxx-yyy-zzz-111-222222222222",
      "shiftPatternId": "pppppp-qqq-rrr-sss-tttuuvvvwwwwx"
    }
    // ... more shifts ...
  ]
}

この出力は、作成されたシフトのリストです。 各シフトには、日付、開始時間、終了時間、担当職員ID、教室ID、シフトパターンIDが含まれています。

この入力形式と出力形式は、あくまでも例です。 実際のアプリでは、さらに詳細な情報が必要になったり、出力形式が変更される可能性があります。 また、エラー処理なども考慮する必要があります。

5. 入力と出力を整理して、プロンプトを考える。

流れとしては、 1. 子供の人数を元に、シフトパターン毎に必要な人数を計算する。 2. その日出勤する職員の情報(勤務時間、希望の時間帯、経験年数)を取得する。 3. その日にどの職員がどのシフトパターンで出勤するか確定する。

出力形式は以下がよさそう。

{
  "shifts": [
    {
      "date": "2024-02-01",
      "staffId": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
      "shiftPatternId": "pppppp-qqq-rrr-sss-tttuuvvvwwwwx"
    },
    {
      "date": "2024-02-01",
      "staffId": "f0e9d8c7-b6a5-4321-9876-543210fedcba",
      "shiftPatternId": "pppppp-qqq-rrr-sss-tttuuvvvwwwwx"
    }
    // ... more shifts ...
  ]
}

6. もう一回inputのデモデータの出力をお願いする。

質問

以下のようにあなたに計算していただくために、必要なinputの形式と、その形式に則ったデモデータをください。

shiftPatternsは3つ準備してください。
また、希望時間帯の考慮もしてください。


1. 子供の人数を元に、シフトパターン毎に必要な人数を計算する。
2. その日出勤する職員の情報(勤務時間、希望の時間帯、経験年数)を取得する。
3. その日にどの職員がどのシフトパターンで出勤するか確定する。

出力形式
===
{
  "shifts": [
    {
      "date": "2024-02-01",
      "staffId": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
      "shiftPatternId": "pppppp-qqq-rrr-sss-tttuuvvvwwwwx"
    },
    {
      "date": "2024-02-01",
      "staffId": "f0e9d8c7-b6a5-4321-9876-543210fedcba",
      "shiftPatternId": "pppppp-qqq-rrr-sss-tttuuvvvwwwwx"
    }
    // ... more shifts ...
  ]
}
===

結果のデモデータ

[
  {
    "date": "2024-03-08",
    "classroom": { "id": "classroom-a", "numberOfChildren": 20 },
    "staffingStandard": { "morning": { "numberOfChildren": 10, "requiredStaff": 2 }, "afternoon": { "numberOfChildren": 15, "requiredStaff": 3 } },
    "availableStaff": [
      // 勤務可能時間と希望時間帯を組み合わせたデータ
      { "id": "staff-1", "experienceYears": 5, "preferredShift": "morning", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-2", "experienceYears": 2, "preferredShift": "afternoon", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-3", "experienceYears": 3, "preferredShift": "morning", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-4", "experienceYears": 1, "preferredShift": "afternoon", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-5", "experienceYears": 4, "preferredShift": "morning", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-6", "experienceYears": 6, "preferredShift": "afternoon", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-7", "experienceYears": 2, "preferredShift": "morning", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-8", "experienceYears": 3, "preferredShift": "afternoon", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-9", "experienceYears": 1, "preferredShift": "morning", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-10", "experienceYears": 4, "preferredShift": "afternoon", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] }
    ],
    "shiftPatterns": [
      { "id": "pattern-a", "name": "午前", "startTime": "08:00", "endTime": "12:00" },
      { "id": "pattern-b", "name": "午後", "startTime": "12:00", "endTime": "17:00" },
      { "id": "pattern-c", "name": "一日", "startTime": "08:00", "endTime": "17:00" }
    ]
  }
]

7. デモデータで実際に完成したプロンプトを試してみる。

質問

以下の計算をして、1日の保育園で勤務する人員のシフトを調整してください。
1. 子供の人数を元に、シフトパターン毎に必要な人数を計算する。
2. その日出勤する職員の情報(勤務時間、希望の時間帯、経験年数)を取得する。
3. その日にどの職員がどのシフトパターンで出勤するか確定する。

入力情報
===
{
    "date": "2024-03-08",
    "classroom": { "id": "classroom-a", "numberOfChildren": 20 },
    "staffingStandard": { "morning": { "numberOfChildren": 10, "requiredStaff": 2 }, "afternoon": { "numberOfChildren": 15, "requiredStaff": 3 } },
    "availableStaff": [
      { "id": "staff-1", "experienceYears": 5, "preferredShift": "morning", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-2", "experienceYears": 2, "preferredShift": "afternoon", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-3", "experienceYears": 3, "preferredShift": "morning", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-4", "experienceYears": 1, "preferredShift": "afternoon", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-5", "experienceYears": 4, "preferredShift": "morning", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-6", "experienceYears": 6, "preferredShift": "afternoon", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-7", "experienceYears": 2, "preferredShift": "morning", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-8", "experienceYears": 3, "preferredShift": "afternoon", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-9", "experienceYears": 1, "preferredShift": "morning", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] },
      { "id": "staff-10", "experienceYears": 4, "preferredShift": "afternoon", "workAvailability": [{ "dayOfWeek": "Friday", "startTime": "08:00", "endTime": "17:00" }] }
    ],
    "shiftPatterns": [
      { "id": "pattern-a", "name": "午前", "startTime": "08:00", "endTime": "12:00" },
      { "id": "pattern-b", "name": "午後", "startTime": "12:00", "endTime": "17:00" },
      { "id": "pattern-c", "name": "一日", "startTime": "08:00", "endTime": "17:00" }
    ]
  }
===


出力形式は以下でお願いします。
===
{
  "shifts": [
    {
      "date": "2024-02-01",
      "staffId": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
      "shiftPatternId": "pppppp-qqq-rrr-sss-tttuuvvvwwwwx"
    },
    {
      "date": "2024-02-01",
      "staffId": "f0e9d8c7-b6a5-4321-9876-543210fedcba",
      "shiftPatternId": "pppppp-qqq-rrr-sss-tttuuvvvwwwwx"
    }
    // ... more shifts ...
  ]
}
===

回答

与えられた入力データに基づいて、シフトを割り当てます。  このアルゴリズムは、単純な優先順位付けに基づいており、最適化は行っていません。  より高度なアルゴリズムが必要な場合は、追加のロジックを実装する必要があります。


**シフト割り当てアルゴリズム:**

1. **必要人数の計算:** 園児数に基づいて、午前と午後の必要人数を計算します。
2. **希望時間帯優先:**  希望時間帯に合う職員を優先的に割り当てます。
3. **残りの職員の割り当て:**  希望時間帯に合う職員が不足している場合は、残りの職員を割り当てます。
4. **一日勤務の職員:**  午前と午後の両方で人員が不足している場合、一日勤務のシフトパターンを割り当てます。
5. **人員不足のチェック:**  必要人数を満たせていない場合はエラーを返します。


**出力:**

json
{
  "shifts": [
    {
      "date": "2024-03-08",
      "staffId": "staff-1",
      "shiftPatternId": "pattern-a"
    },
    {
      "date": "2024-03-08",
      "staffId": "staff-3",
      "shiftPatternId": "pattern-a"
    },
    {
      "date": "2024-03-08",
      "staffId": "staff-2",
      "shiftPatternId": "pattern-b"
    },
    {
      "date": "2024-03-08",
      "staffId": "staff-4",
      "shiftPatternId": "pattern-b"
    },
    {
      "date": "2024-03-08",
      "staffId": "staff-6",
      "shiftPatternId": "pattern-b"
    },
        {
      "date": "2024-03-08",
      "staffId": "staff-5",
      "shiftPatternId": "pattern-c"
    }

  ]
}



**アルゴリズムの改善点:**

* **希望時間帯の柔軟な対応:**  希望時間帯以外も考慮する仕組みを追加する。 例えば、希望時間帯以外のシフトに割り当てる際のペナルティを設ける。
* **経験年数の考慮:**  経験年数の長い職員を優先的に割り当てる。
* **職員のスキル考慮:**  職員のスキル(ピアノ、英語など)を考慮した人員配置を行う。
* **休暇の考慮:**  職員の休暇情報を考慮する。
* **最適化アルゴリズムの導入:**  より高度な最適化アルゴリズム(制約充足問題ソルバーなど)を用いることで、より効率的で公平なシフトを作成する。
* **エラー処理:**  人員不足などのエラーを適切に処理する。
* **複数教室への対応:** 複数教室を同時に考慮したシフト作成を行う。


この例では、単純なアルゴリズムでシフトを割り当てています。  より複雑な要件や、最適化されたシフト作成を行うためには、より高度なアルゴリズムとデータ構造が必要となります。  特に、大規模な保育園や、複雑な条件下でのシフト作成を行う場合は、専門的な知識と技術が必要となる可能性があります。


この出力は、与えられた入力データと単純なアルゴリズムに基づいて作成されています。  より最適なシフトを作成するためには、より高度なアルゴリズムの導入が必要です。  また、このアルゴリズムは、人員が不足している場合にエラー処理を行っていません。  エラー処理を追加する必要があります。

感想

github copilotぐらいしかLLM触ったことない自分が、2時間ぐらいしかやってないけど、DB設計が荒削りだけどできてるから、あとはUI作ればしっかりしたデモができそう。(ここからの精度向上が難しそうだけど...) やっぱりなんといっても、実際のシフト計算するところのロジックをLLMになんとなく生成してもらってるから、日本語でこう改善してほしいみたいな感じの相談を、保育士の妻と一緒にできて楽しかった。

聞いたところ、それぞれの園独自のルールみたいなのが結構あるっぽいので、それも園独自のプロンプトみたいな感じで表現できれば、Saasプロダクトでも適用できそう。

実際商品レベルにするにはUIをコドモン様レベルまで作り込まないといけないだろうから、将来はバックエンドとかより、UI側に人の力が必要になるのかな? シフト管理 | 保育園・幼稚園・小学校など対応 | CoDMON(コドモン)

qodo-coverの調査

全然Agent周りのキャッチアップができていないのと、品質担保について興味があるので、自動テストをLLMで生成するOSSライブラリについて調べてみた。

GitHub - qodo-ai/qodo-cover: Qodo-Cover: An AI-Powered Tool for Automated Test Generation and Code Coverage Enhancement! 💻🤖🧪🐞

前提として、LLM機能の出力の妥当性を評価する必要がある。

LLMアプリケーションの評価入門〜基礎から運用まで徹底解説〜

この際に、以下のようなLLMやAgentを使って出力自体も評価する方法が最近どんどん出ているそうだが、今回はシンプルにUnitTestのCoverageとテストの実行結果をチェックして評価をしているっぽい。 - LLM-as-a-Judge - Agent-as-a-Judge

全体の流れとしては、こんな感じとのこと。

手順:

  1. 目標カバレッジ到達または最大反復回数到達までループ: 所望のカバレッジ率が達成されるか、最大反復回数が実行されるまで、以下のステップを繰り返します。

  2. 新規テストの生成: コードカバレッジを向上させるための新しいテストケースを生成します。 このステップでは、カバレッジツールやAIを用いたテスト生成技術などが使用される可能性があります。

  3. 新規テストの検証: 生成された各テストケースを実行し、期待通りの結果が得られるか検証します。 このステップでは、テストケースが正しく動作し、意図したコード部分をカバーしていることを確認します。

  4. テスト結果のデータベースへの挿入: 各テストケースの実行結果(成功/失敗、カバレッジ情報など)をデータベースに格納します。 これにより、テスト履歴の追跡と分析が可能になります。

  5. 反復回数のインクリメント: 反復回数を1つ増加させます。

  6. 目標カバレッジの確認: 現在のカバレッジ率が目標カバレッジ率に到達したかどうかを確認します。

  7. 目標カバレッジ到達時の処理: 目標カバレッジ率に到達した場合、最終的なカバレッジ率をログに出力します。

  8. 最大反復回数到達時の処理: 最大反復回数に到達した場合、厳格なカバレッジが指定されている場合は、失敗メッセージをログに出力します。

  9. トークン使用量に関するメトリクスの提供: テスト生成プロセス全体で使用されたトークン(AIモデルを使用している場合)の総量に関するメトリクスを提供します。

  10. レポートの生成: プロセス全体の概要、カバレッジ率の推移、テスト結果、トークン使用量などの情報をまとめたレポートを生成します。

レポート結果のHTML

詳細実装確認

新規テストの生成、新規テストの検証がどうなっているかが気になるのでそこを読んでみた。

新規テストの生成

プロンプトはここにある。 https://github.com/qodo-ai/qodo-cover/blob/58a416e5f414b1d43db9722a2fbeeb1ccbd1fe4a/cover_agent/settings/test_generation_prompt.toml プロンプトにある変数部分はコマンド実行時の引数で指定する。

cover-agent \
  --source-file-path "templated_tests/typescript_calculator/src/modules/Calculator.ts" \
  --test-file-path "templated_tests/typescript_calculator/tests/Calculator.test.ts" \
  --project-root "templated_tests/typescript_calculator" \
  --code-coverage-report-path "templated_tests/typescript_calculator/coverage/cobertura-coverage.xml" \
  --test-command "npm run test" \
  --test-command-dir "templated_tests/typescript_calculator" \
  --coverage-type "cobertura" \
  --desired-coverage 70 \
  --max-iterations 1

included_filesに必要なコンテキスト(DBテーブルの定義とかかな?)を入れるようにもできてたので、この辺も試してみたい。もしかしたらIntegration Testにも使えるのかも?

出力結果を指定のyaml形式で出してもらってそれをパースして、テキストを取得して、DB(sqllite)に保存してた。

新規テストの検証

以下のように、既存のテストに対して、生成されたテストを追加して、自動テストを実行してるっぽい。 coverage結果の表示形式については、cobertura, lcov, jacocoに対応していてパースしてカバレッジを取得していた。

  1. 前提: 各生成されたテストは、独立した自己完結型テストであると仮定します。

  2. テストコードと追加インポートの抽出: 生成されたテストからテストコードと追加のインポートを抽出します。

  3. 追加インポートのクリーンアップ (必要に応じて): 必要に応じて、追加のインポートをクリーンアップします(重複の削除など)。

  4. テストコードとインポートの挿入位置の特定: テストファイルへの挿入位置(行番号)を特定します。

  5. テストコードのインデント調整: テストコードのインデントを、テストファイルのインデントスタイルに合わせます。

  6. テストコードと追加インポートのテストファイルへの挿入: 特定した行番号に、テストコードと追加インポートをテストファイルに挿入します。

  7. テストの実行 (Runner クラス使用): Runner クラスを使用してテストを実行します。

  8. 終了コードによるテスト成功/失敗判定: 終了コードをチェックし、テストが成功したか失敗したかを判定します。

  9. テスト失敗時のロールバック: テストが失敗した場合、テストファイルを元の状態にロールバックし、失敗をログに記録します。

  10. テスト成功時のカバレッジ増加確認 (CoverageProcessor クラス使用): テストが成功した場合、CoverageProcessor クラスを使用してコードカバレッジが増加したかどうかを確認します。

  11. カバレッジ増加なしの場合のロールバック: カバレッジが増加していない場合、テストファイルをロールバックし、失敗をログに記録します。

  12. カバレッジ増加時のカバレッジ更新と成功ログ: カバレッジが増加した場合、現在のカバレッジを更新し、成功をログに記録します。

  13. 例外処理: 検証プロセス中に発生する可能性のある例外を処理し、エラーをログに記録し、必要に応じてテストファイルをロールバックします。

実際に動かしてみた

typescriptのデモを利用した。 openAIのAPI Keyが必要なので課金が必要...iteration1回で0.02ドルぐらいかかった。

docker build . -t qodo
docker run -it qodo /bin/bash -c "make installer && while true; do sleep 1; done"
docker ps
docker exec <container_id> -it /bin/bash
export OPENAI_API_KEY=

cd templated_tests/typescript_calculator

curl -fsSL https://deb.nodesource.com/setup_23.x | bash - \
 && apt-get update \
 && apt-get install -y --no-install-recommends nodejs \
 && rm -rf /var/lib/apt/lists/*

npm install
npm test

dist/cover-agent \
  --source-file-path "templated_tests/typescript_calculator/src/modules/Calculator.ts" \
  --test-file-path "templated_tests/typescript_calculator/tests/Calculator.test.ts" \
  --project-root "templated_tests/typescript_calculator" \
  --code-coverage-report-path "templated_tests/typescript_calculator/coverage/cobertura-coverage.xml" \
  --test-command "npm run test" \
  --test-command-dir "templated_tests/typescript_calculator" \
  --coverage-type "cobertura" \
  --desired-coverage 70 \
  --max-iterations 1

感想

思ったよりシンプルだったけど、テストがサクッと追加されてた。 Copilotでもテストの提案とかはしてくれてて便利だったけど、このように自動でUnitTest増やして、レビューだけするみたいな感じが既に現実なんだと感じた。 一方で、テストを書きやすい設計(この辺が興味ある https://levtech.jp/media/article/column/detail_558/ )をいかにするかも大事そうだと感じた。

OpenSearch調査

概要

OpenSearch(ElasticSearch)について調査して、知識を深める

OpenSearchって?

AWS と Elastic社が争って、AWSで提供するElasticSearchのサービスは、OpenSearchという名前になってる。

codezine.jp

Elasticsearch 7.10.2 から派生したので、ほとんどElasticSearchと一緒のことできる。

SQLが使える SQL を用いた Amazon OpenSearch Service のデータのクエリを行う - Amazon OpenSearch Service

Localで試してみたい

この記事通りにすると簡単に試せる!

OpenSearchをローカル環境でDockerを利用して構築する | DevelopersIO

ElasticSearchって?

こちらの記事読めばOK!

Elasticsearch入門 の記事一覧 | DevelopersIO

ちなみに、Elastic社が提供してるElasticStackというサービスの組み合わせがあって、これ使うとログ分析が捗るらしい(試したことしかない)

Elastic Stack = Elasticsearch、Kibana、Beats、Logstash | Elastic

競合は?

Algolia, Solr, Groonga など

どういう時に使う?

ElasticSearchは全文検索エンジンであるので、文字を検索したいときに使う。

N-gram形態素解析という二つの方法が利用できる。詳しい説明は下のサイトがわかりやすかった。

第6回 N-gramと形態素解析との比較 | gihyo.jp

ちなみに、MySQLPostgreSQLn-gramは既にできる。日本語の形態素解析Mecabを使えばできるっぽい。

なので、既にRDBMSを使ってる場合は、まずはRDBMSでやり切る方が先そう。

一方で、データが増えてきたり、スコアリングとか、ベクトル検索みたいな機能を使いたい時は良さそう?(RDBMSでもできるのかもしれないけど、知識がなくてわからない)

Elasticsearchの機能一覧 | Elastic

ベクトル検索だと、この記事がすごく勉強になる。ベクトルを保存して、類似度を検索できる。

Elasticsearchを用いて類似度ベクトル検索をやってみてわかったこと - ログミーTech

スコアリングは使ったことがあって、検索文字とどれだけ一致してるかを返してくれる。

function_scoreっていうのを使って、(PV数 x 1 + お気に入り数 x 10 + 一致度) でソートするみたいな順でデータを取得できたりできる。

RDBMSでなく、DynamoDBを永続化層と使っている場合は、ElasticSearchを検索に使う方法は結構記事が出てくるから一般的っぽい。

DynamoDBとElasticsearchをエンプラで本気で使ってみた2018年を非機能面で振り返る - Qiita

実際にIndexを作ってみよう!

Indexがテーブル設計。indexにmappingを設定するのだが、これが非常に重要となる。

とりあえずやってみる

Product {
  id: number
  manufactureId: number
  name: string
  description: string
}

Manufacture {
  id: number
  name: string
}

Event {
  eventMasterId: number
  userId: number
  timestamp: datetime
}

ざっくりやるとこんな感じ?

PUT /products
{
  "mappings": {
    "properties": {
      "productId": {
        "type": "integer"
      },
      // nameは完全一致も、部分一致でも検索したいので、text型とkeyword型の両方を作成する。
      "name": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      },
      // descriptionはkuromojiで日本語での形態素解析での検索を可能に
      "description": {
        "type": "text",
        "analyzer": "kuromoji"
      },
      "manufacture": {
        "properties": {
          "manufactureId": {
            "type": "integer"
          },
          // 企業名は完全一致での検索しかされないと判断して、keyword型のみ利用
          "name": {
            "type": "keyword"
          }
        }
      },
      // event情報も、productsのindexに紐づけることで、プロダクト検索を容易にする。
      // nestedを使うことで、それぞれの子要素に対する検索を可能にする
      "events": {
        "type": "nested",
        "properties": {
          "eventMasterId": {
            "type": "integer"
          },
          "userId": {
            "type": "integer"
          },
          "timestamp": {
            "type": "date"
          }
        }
      }
    }
  }
}

こんな感じで検索できる

GET /products/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "term": {
            "name.keyword": "商品A"
          }
        },
        {
          "nested": {
            "path": "events",
            "query": {
              "bool": {
                "filter": [
                  {
                    "term": {
                      "events.eventMasterId": "1"
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  }
}

懸念点

OpenSearchのDocumentには見つからなかったが、ElasticSearchのDocumentには、nestedの中のObjectが10000を上限とするように推奨されている。

なので、10000過ぎたらtimestampが古いものから削除するとかしても良いが、上手く検索できない。

パターン1

イベント定義ID毎に、最終イベント発生日と、イベントを発生したUserIdの一覧を登録するようにする。

これで、例えば直近でイベントAが発生した商品検索はできるし、ユーザーAがイベントAを発生した商品検索もできる。

だが、これだと指定の日時でイベントを発生した商品の検索ができない。

{
  "events": {
    "type": "nested",
    "properties": {
      "eventMasterId": {
        "type": "integer"
      },
      "lastEventedAt": {
        "type": "date"
      },
      "userIds": {
        "type": "keyword"
      }
    }
  }
}

パターン2

日付毎に、発生イベントの配列と、イベントを発生したユーザーの一覧を持たせる。

一定の期間毎にデータは削除していく。

これで、大体の検索はできそう。だが、指定期間にX回イベントが発生したかという検索はできない。

{
  "events": {
    "type": "nested",
    "properties": {
      "date": {
        "type": "date"
      },
      "eventMasterIds": {
        "type": "keyword"
      },
      "userIds": {
        "type": "keyword"
      }
    }
  }
}

パターン3

日付でgroupして、もう一回nestedにするようにしてみた。

おそらく、イベント発生を検索条件に入れる場合は、先に日付で絞り込みするだろうから、絞り込んだ後に、回数で検索できる。

思いつく感じはこれで網羅できたが、本当にこれでいけるのだろうか...

あと、そもそもイベントの更新があるたびにupdateするのは、非効率なので1日1回の更新とかになりそうだが、大丈夫?

{
  "events": {
    "type": "nested",
    "properties": {
      "date": {
        "type": "date"
      },
      "occurs": {
        "type": "nested",
        "properties": {
          "eventMasterId": {
            "type": "integer"
          },
          "count": {
            "type": "integer"
          },
          "userIds": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

パターン4

新しくeventだけのindexを作っちゃって、Joinをする。

Join field type | Elasticsearch Guide [8.5] | Elastic

これが一番簡単そうだし自然だが、安易にリレーションを使うのはパフォーマンスの問題があるとのこと。

これだけならおそらく大丈夫そうだが、パフォーマンステストはしたほうが良さそう。

リレーショナルモデルを複製するために、複数レベルのリレーションを使用することはお勧めしません。各レベルのリレーションは、クエリ時にメモリと計算のオーバーヘッドを増加させる。検索性能を上げるには、データを非正規化するのがよいでしょう。

まとめ

色々調べたけど、実際にどんなmappingを作るのがベストか分からない...

更新頻度を考えつつ、どういう検索をしたいのかというのをちゃんと考えてから作る必要がありそう。

RedshiftなどのDWHとの使い分け

ElasticSearchは、リアルタイムに近い検索や全文検索が可能ですが、Redshiftほどに大量の時系列データを扱うことは厳しい。億単位のデータを瞬時に返そうとすると流石に勝てないやってなっちゃうっぽい。

一方で、RedshiftはInsertやUpdateが得意じゃない(ちなみにBigQueryはUpdateができない)ので、リアルタイムな更新が求められる場合は辛い。一方で、億単位のデータでも簡単にレスポンスが数秒で返ってくる。

AWS リソースメモ

目的

転職して、インフラ周りをガッツリ触るようになった。

色々知識が増えたので、忘れないようにメモしておく。

Tips

Terraform

  • 手動で、AWS コンソールから、リソースを作った場合は、Terraform import をすれば良い。  - やり方はterraformのドキュメントの一番下に書いてある。
  • 少なくとも、RDSといったストレージ系や、Iam、VPCなどは、terraform stateを分離しておいた方が良い。
    • 環境毎に使わないリソースとかできるし、terraform apply が早い
    • Remote Stateを設定しておけば、他のStateのoutputを利用できる

AWS Session Manager

  • session managerを使えば、SSHの設定とか、踏み台サーバーとかがいらない。
  • ポートのリダイレクトができるので、LocalからVPC内のRDSにアクセスするということも簡単。
  • EC2じゃなくて、Fargate でも使える!

Aurora

  • IAMを用いた認証ができる。パスワードが不要なので、流出する心配がない。  - ただ、IAMユーザーを作ったりしないといけないので、ちょっとめんどい。  - ユーザーが IAM 認証情報で Amazon RDS に接続できるようにする
  • Performance Insightsは神。とりあえず入れおくと良い。
    • SlowQueryはもちろんのこと、Lockの状況とかも確認できる。

API Gateway

  • Rest APIを使って、直接Kinesis streamに流したり、SQSにQueue入れたりできる。
  • Mock機能もあるので、公開されてるAPIを使った負荷テストしたい時に、代用のAPIを作ることができる。

S3

  • S3でサイトを作りたい時は、Static Website HostingをONにして使うと思う。が、これだと吐き出されるURLがhttpになる。  - なので、CloudFrontを前段に入れるのが多いが、その場合、もしIP制限したい場合はWAFが必要になる。
  • 一方で、Static Website HostingをOFFにしちゃえば、IP制限はS3の方でできるので、簡単!
    • 社内からしかアクセスしないし、そんな使われないけど、HTTPSにしないといけないって時(滅多にないと思うけど、今回あった)におすすめ。S3にHTML置いて、サイト作るなら、Static Website Hostingを使うって先入観が強すぎて、盲点だった...Storybookのpreview環境作る時に使ってたのに...

Kinesis Stream

  • Kinesisには、Aggregationという考え方がある
  • Aggregationした後は、De-Aggregationしないといけない。
    • こっちは、NodeとかGoとかのライブラリがある。
    • Kinesis Firehoseを使って、Kinesis streamのデータを、各リソースに渡す場合は、レコードの集約を解除してくれるので便利!
  • Kinesis StreamはDynamoDBのレコード追加とかも、取得してくれるので便利!

Memory DB

  • 従来AWSでRedisを使うときは、ElasticCacheを使っていたが、MemoryDBというのものある。
  • MemoryDBは、データの耐久性があるから、RDSのように永続的なデータを保存して、読み取りできる。
    • 一方で、ElasticCacheと比べて、通常の書き込みが遅かったり、ClusterModeを強制される。
  • DynamodB+DAXの代替として検討したり、RDB+キャッシュRedisなシステムを MemoryDB に集約するといったケースで使う感じっぽい

rails config.force_ssl についてメモ

railsdoc.com

config.force_ssl = true

にすると、以下のようにActionDispatch::SSLというmiddlewareが有効になる

if config.force_ssl
  middleware.use ::ActionDispatch::SSL, **config.ssl_options,
    ssl_default_redirect_status: config.action_dispatch.ssl_default_redirect_status
end

やってることは以下

rails/ssl.rb at v7.0.2.2 · rails/rails · GitHub

request.ssl?がfalseの場合、httpsにリダイレクトする

if request.ssl?
   ...
else
   return redirect_to_https request unless @exclude.call(request)
   ...
end

Cookieにsecure 属性をつける

def flag_cookies_as_secure!(headers)
  if cookies = headers["Set-Cookie"]
    cookies = cookies.split("\n")

    headers["Set-Cookie"] = cookies.map { |cookie|
      if !/;\s*secure\s*(;|$)/i.match?(cookie)
        "#{cookie}; secure"
      else
        cookie
      end
    }.join("\n")
  end
end

HTTP Strict Transport Security (HSTS) headerを追加する。

# https://tools.ietf.org/html/rfc6797#section-6.1
def build_hsts_header(hsts)
  value = +"max-age=#{hsts[:expires].to_i}"
  value << "; includeSubDomains" if hsts[:subdomains]
  value << "; preload" if hsts[:preload]
  value
end

request.ssl? の中身はというと、Rack::Request::Helpers にあるっぽい。 rack/request.rb at v2.2.2 · rack/rack · GitHub

こんな感じで、headerとか、X-Forwarded-Forにも対応してる。

def ssl?
   scheme == 'https' || scheme == 'wss'
end

def scheme
    if get_header(HTTPS) == 'on'
        'https'
    elsif get_header(HTTP_X_FORWARDED_SSL) == 'on'
        'https'
    elsif forwarded_scheme
        forwarded_scheme
    else
        get_header(RACK_URL_SCHEME)
    end
end

def forwarded_scheme
    allowed_scheme(get_header(HTTP_X_FORWARDED_SCHEME)) ||
    allowed_scheme(extract_proto_header(get_header(HTTP_X_FORWARDED_PROTO)))
end

なので、AWSでよくある、HTTPS -> ALB -> HTTP -> EC2 といった通信の場合は、内部ではHTTP通信でもRedirectされない。

extconf.rbについて調べたことメモ

目的

RubyのgemのC実装を読む際に、extconf.rb周りでエラーが起きるのだが、そもそもこれが何するものかが良くわからなかったので調べた。

結論

これを読めば良い。 docs.ruby-lang.org

自分用のメモは間違えてるかもしれないので、注意してください。

自分用にメモ

Ruby の拡張ライブラリのための Makefile を作成するファイル。mkmfライブラリをrequireして、このrbスクリプトを実行して、Makefileが生成される。 ヘッダファイルの存在チェック、ライブラリの存在チェックなどextconf.rbで行う。

// gemspec にこんな感じでファイルを指定する。
spec.extensions    = ['ext/extconf.rb']

こうすると、gem install時に、make コマンドを実行して、実行環境に合わせてコンパイルしてくれる。

詳細はこのサイトに詳しく書いてあるので参考に。

gem install で C拡張をビルドする流れを追ってみた - sonots:blog

dir_config というメソッドがあるが、これを用いると、依存packageを実行する際のパスを設定することができる。

docs.ruby-lang.org

例えば、duckdbだと、gem install時に、以下のように設定することで、gem毎に参照するディレクトリを設定できる。

--with-duckdb-include=/duckdb_header_directory
--with-duckdb-lib=/duckdb_library_directory

あるいは、/duckdb_directoryの下に、includeとlibディレクトリがあって、includeの方にはheaderファイルがある、libの方には共有ライブラリがあるという状態なら以下の設定だけでいける。

--with-duckdb-dir=/duckdb_directory

homebrewだと、こんな感じで指定できるはず!

--with-duckdb-dir=$(brew --prefix duckdb)

もし、gem毎に設定する必要がなければ、CFLAGSとLDFLAGSを環境変数で設定しても参照できる。

CFLAGSとLDFLAGSについて

自信あまりないので、違ったらご指摘ください...

CFLAGS: Cのsourceファイルをbuildする時に使う(Cのコンパイラーに渡すフラグ)

コンパイラーにCのビルドしたい標準の場所以外にあるファイルの位置を教えてあげる時に使える。

LDFLAGSS: ld(リンカー)に渡すフラグ

ビルドする際は、複数の.cファイルを.soファイルにまとめている。

リンカーは複数のビルド済みのファイルを、くっつけて使えるようにする。

そのリンカーに、標準の場所以外にあるファイルの位置を教えてあげるときに、LDFLAGS -L{path} という形で使える。

Rails & mysql2 gem

Mac & homebrewを利用している環境で、Railsでmysql2のgemを利用しようとすると、毎回ld: library not found for -lsslというエラーが出る。

これは、下の記事にあるように、ライブラリが見つからないとのことなので、pathを通してあげればいい。

【Ruby】M1macでmysql2がインストールできないとき

といった感じで、個々人では解決できる。

ここで、もしexconf.rbを修正するとすると、以下のように解決できる。

Dynamically set Homebrew-installed OpenSSL flag by olivierlacan · Pull Request #1204 · brianmario/mysql2 · GitHub

// homebrewを使っていて、OSがdarwin = macの場合、LDFLAGSに値を追加している。
if RUBY_PLATFORM =~ /darwin/ && system("command -v brew")
  openssl_location = `brew --prefix openssl`.strip
  if openssl_location
    $LDFLAGS << " -L#{openssl_location}/lib" 
  end
end

例えば、LDFLAGS="-L/opt/homebrew/opt/openssl/bin" と設定すると、/opt/homebrew/opt/openssl/bin以下のライブラリを検索してくれる。

こうすることで、毎回$ bundle config --local build.mysql2 "--with-ldflags=-L/opt/homebrew/opt/openssl/lib"みたいに手動でやらなくてもエラーなくインストールができる。

現時点のmysql2最新版である0.5.3では、まだexconf.rbの修正がリリースされてないが、もう少ししたらここに悩むことは無くなりそう!