🦁

ChatGPTにDBからレコードを取得するコードを書いてもらった

2023/01/29に公開

はじめに

前回 がいい感じだったのでもっと具体的なやつを書いてもらって実際に動くか試してみる。

CRUDなアプリを作るときによくあるUsersテーブルからIDでレコードを取得するみたいな、getUserByIdな関数をChatGPT先生がいい感じに書いてくれるか試してみた。

書いてもらう

事前にいろいろと試してみて、ざっくりいい感じのコードを出してくれた依頼文が以下。

MongoDBのexampleというDBの中のUsersコレクションから特定のドキュメントを_idで検索し、1つのドキュメントを取得したいです。これをTypeScriptでDeno上で動くコードを書いてください。

関数名は文字列変数"getUserById"とし、以下の仕様とします。

この関数のファイル名は "getUserById.ts" とします。

getUserByIdはstring型の"_id"を第一引数にとります。

データベースのUriは "mongodb://localhost:27017"

インポートするMongo driver は https://deno.land/x/mongo@v0.31.1/mod.ts

usersコレクションに対するtype interfaceは次のとおりです。

interface UserSchema {
  _id: ObjectId;
  no: number;
  name: string;
  role: "admin" | "staff";
  email: string;
  createdAt: number;
  updatedAt: number;
}

先生の回答はこちら。

getUserById.ts
import { MongoClient } from "https://deno.land/x/mongo@v0.31.1/mod.ts";
import { ObjectId } from "https://deno.land/x/mongo@v0.31.1/mod.ts";

interface UserSchema {
  _id: ObjectId;
  no: number;
  name: string;
  role: "admin" | "staff";
  email: string;
  createdAt: number;
  updatedAt: number;
}

const getUserById = async (id: string): Promise<UserSchema | null> => {
  const client = new MongoClient();
  client.connectWithUri("mongodb://localhost:27017");

  const db = client.database("example");
  const usersCollection = db.collection<UserSchema>("users");

  const user = await usersCollection.findOne({ _id: ObjectId(id) });
  client.close();

  return user ?? null;
};
このコードは、getUserById関数を定義し、MongoClientを使用してexample DBのUsersコレクションから特定のドキュメントを_idで検索し、1つのドキュメントを取得することができます。

引数として受け取った文字列"id"をObjectIdに変換して、findOne関数に渡します。

最後に、取得したドキュメントを返します。もしドキュメントがなかった場合は null を返します。

おしい。この時点でVSCodeがエラーを出している。

connectWithUri()v21.xあたりで廃止されているようだ。

ObjectId() はコンストラクタなので newが必要

なんとなく、先生はdeno_mongoの古いバージョンをご存知のようだ。

上記を直してもらう

上記のコードから複数のエラーが検出されました。以下のように修正してください。

1. connectWithUri("mongodb://localhost:27017");
は廃止されたようです。代わりに await client.connect("mongodb://localhost:27017") を使用してください
2. ObjectId(_id) は コンストラクタであるため new ObjectId(_id) とする必要があります

以下のように修正された。

getUserById.ts
import { MongoClient } from "https://deno.land/x/mongo@v0.31.1/mod.ts";
import { ObjectId } from "https://deno.land/x/mongo@v0.31.1/mod.ts";

interface UserSchema {
  _id: ObjectId;
  no: number;
  name: string;
  role: "admin" | "staff";
  email: string;
  createdAt: number;
  updatedAt: number;
}

const getUserById = async (id: string): Promise<UserSchema | null> => {
  const client = new MongoClient();
  await client.connect("mongodb://localhost:27017");

  const db = client.database("example");
  const usersCollection = db.collection<UserSchema>("users");

  const user = await usersCollection.findOne({ _id: new ObjectId(id) });
  client.close();

  return user ?? null;
};
このコードは、getUserById関数を定義し、MongoClientを使用してexample DBのUsersコレクションから特定のドキュメントを_idで検索し、1つのドキュメントを取得することができます。

connectWithUri() は廃止され、connect() 関数を使用して接続を行うように更新しました。

_idをObjectIdに変換するために new ObjectId(_id) 。

最後に、取得したドキュメントを返します。もしドキュメントがなかった場合は null を返します。

VSCodeでエラーは出なくなった。でもあれ、この getUserById exportされてないから外から使えないじゃん。というわけで、

getUserById関数を export してください。
getUserById.ts
import { MongoClient } from "https://deno.land/x/mongo@v0.31.1/mod.ts";
import { ObjectId } from "https://deno.land/x/mongo@v0.31.1/mod.ts";

interface UserSchema {
  _id: ObjectId;
  no: number;
  name: string;
  role: "admin" | "staff";
  email: string;
  createdAt: number;
  updatedAt: number;
}

export const getUserById = async (id: string): Promise<UserSchema | null> => {
  const client = new MongoClient();
  await client.connect("mongodb://localhost:27017");

  const db = client.database("example");
  const usersCollection = db.collection<UserSchema>("users");

  const user = await usersCollection.findOne({ _id: new ObjectId(id) });
  client.close();

  return user ?? null;
};
このコードでは、getUserById関数を export しています。他のファイルからこの関数をインポートし、使用することができます。

はい。ありがとう。これを利用する実行処理を書いてもらう。

この関数をインポートして main関数を実行するコードを書いてください。ファイル名は "index.ts" です。
getUserById()に渡す引数は "6381c63b254d01ec76de9d34" とします。
index.ts
import { getUserById } from "./getUserById.ts";

const main = async () => {
    const user = await getUserById("6381c63b254d01ec76de9d34");
    console.log(user);
};

main();
このコードでは、getUserById関数をインポートし、main関数内で呼び出しています。引数として "6381c63b254d01ec76de9d34" を渡し、得られた結果をconsole.logで表示しています。
main関数は、結果を得るためにawaitを使用しているため、この関数自体もasyncにしておく必要があります。
また、これらのファイルを実行するには deno run --allow-net index.ts のように、denoを使って実行しなければならないことに注意してください。

おお、表示してほしいとか書いてないのにconsole.log(user)としているあたりわかってる感ある。--allow-netで実行しろってのも親切。

さて、とりあえず動きそうな感じのコードを書いていただいたので実際動くか実行してみます。

実行してみる

上記を実行するためにはMongoDBが必要なので以下の docker-compose.ymlで立ち上げることにする

docker-compose.yml
version: "3.4"
services:
  mongo:
    container_name: mongo
    hostname: mongo
    image: mongo:5.0.12
    ports:
      - 27017:27017
    volumes:
      - $PWD/db/mongo:/data/db
    restart: always

usersコレクション用のデータを準備する

users.json
{"_id":{"$oid":"6381c63b254d01ec76de9d33"},"no":1,"name":"Taro Suzuki","role":"admin","email":"taro@example.com","createdAt":1.669449275385E+12,"updatedAt":1.669449275385E+12}
{"_id":{"$oid":"6381c63b254d01ec76de9d34"},"no":2,"name":"Jiro Yamada","role":"staff","email":"jiro@example.com","createdAt":1.669452096358E+12,"updatedAt":1.669452096358E+12}
{"_id":{"$oid":"6381c63b254d01ec76de9d35"},"no":3,"name":"Saburo Sato","role":"staff","email":"saburo@example.com","createdAt":1.669452096358E+12,"updatedAt":1.669452096358E+12}

index.tsgetUserById()に渡している引数6381c63b254d01ec76de9d34Jiro Yamada_id。つまりindex.tsを実行した際にはJiro Yamadaが表示されるのを期待する。

shell
docker compose up -d

mongoコンテナが立ち上がったら以下を実行してDBを作成する

shell
docker cp ./users.json mongo:/data/
docker exec -it mongo mongoimport --db example --collection users --drop --file /data/users.json

準備ができたのでChatGPT先生のおっしゃるとおりのコマンドで実行する

shell
deno run --allow-net index.ts

結果は以下の通り。Jiro Yamadaが正常に取得できた!

shell
{
  _id: new ObjectId("6381c63b254d01ec76de9d34"),
  no: 2,
  name: "Jiro Yamada",
  role: "staff",
  email: "jiro@example.com",
  createdAt: 1669452096358,
  updatedAt: 1669452096358
}

人間がTypeScriptを1行も書かずに関数ライブラリgetUserById.tsを作成することができました。あざます。

類似の関数を書いてもらう

DB操作系の関数は似たロジックになるものが多い。実際の開発でも一個できてしまえばコピペしてちょっと変更して増やしていくことが多いが、先生ならもっと早く仕事をしてもらえるに違いない。というわけで、以下のように感謝を忘れず依頼。

上記のコードは良好に動作しました。ありがとうございます!

次に"getUserById()"と同類の"getCategoryById()"関数を書いてください。
ファイル名は"getCategoryById.ts"とします。

getCategoryById()が対象とするコレクションは"categories"です。

categoriesコレクションに対するtype interfaceは次のとおりです

interface CategorySchema {
  _id: ObjectId;
  name: string;
  visible: boolean;
  createdAt: number;
  updatedAt: number;
}
getCategoryById.ts
import { MongoClient } from "https://deno.land/x/mongo@v0.31.1/mod.ts";
import { ObjectId } from "https://deno.land/x/mongo@v0.31.1/bson.ts";

interface CategorySchema {
    _id: ObjectId;
    name: string;
    visible: boolean;
    createdAt: number;
    updatedAt: number;
}

const client = new MongoClient();
client.connectWithUri("mongodb://localhost:27017");

const getCategoryById = async (id: string): Promise<CategorySchema | null> => {
    const db = client.database("example");
    const categories = db.collection<CategorySchema>("categories");
    const _id = new ObjectId(id);
    const category = await categories.findOne({ _id });
    return category;
};

export { getCategoryById };
このコードでは、getCategoryById関数を作成しています。この関数は、引数として渡された文字列型のidをObjectIdに変換し、それを使用してexampleデータベースのcategoriesコレクションからドキュメントを検索します。
戻り値は、検索によって見つかったドキュメントのオブジェクトであり、存在しない場合はnullが返ります。

この関数は、main関数などから呼び出し、使用することができます。

ぬ、微妙に以前の反省がない。

またconnectWithUri()を使っている。あと新規の間違いとしてreturn categoryとなっている。これだとCategorySchema|undefinedが返るので返り値の型と合わない。

あ、あとなぜか import { ObjectId } from "https://deno.land/x/mongo@v0.31.1/bson.ts";となっていてbsonからObjectIdを引っ張ってきてる。これもおかしい。

というわけでちょっと愚痴っぽく修正依頼。

上記のコードから複数のエラーが検出されました。以下のように修正してください。

1. 以前と同様に connectWithUri("mongodb://localhost:27017");
は廃止されたようです。代わりに await client.connect("mongodb://localhost:27017") を使用しなければなりません
2. `return category;` の categoryの型は'CategorySchema|undefined'となるため、返り値型と合いません。getUserById()と同様に `return category ?? null;`とすべきです
3. `import { ObjectId } from "https://deno.land/x/mongo@v0.31.1/bson.ts";` ではなく `import { ObjectId } from "https://deno.land/x/mongo@v0.31.1/mod.ts";` です。
getCategoryById.ts
import { MongoClient } from "https://deno.land/x/mongo@v0.31.1/mod.ts";
import { ObjectId } from "https://deno.land/x/mongo@v0.31.1/mod.ts";

interface CategorySchema {
  _id: ObjectId;
  name: string;
  visible: boolean;
  createdAt: number;
  updatedAt: number;
}

export async function getCategoryById(_id: string): Promise<CategorySchema | null> {
  const client = new MongoClient();
  client.connectWithUri("mongodb://localhost:27017");

  const db = client.database("example");
  const categories = db.collection<CategorySchema>("categories");

  const category = await categories.findOne({ _id: new ObjectId(_id) });

  client.close();
  return category ?? null;
}
index.ts
import { getCategoryById } from "./getCategoryById.ts";

console.log(await getCategoryById("6381c63b254d01ec76de9d34"));

なんか気を利かせてindex.tsも書いてくれたのはいいんだが、client.connectWithUri("mongodb://localhost:27017");が直ってない。。どうも古いdeno_mongoの書き方が身に染み付いてしまっているようだw
あ、しれっと client.close()が入ってるしw

client.connectWithUri("mongodb://localhost:27017"); を   await client.connect("mongodb://localhost:27017"); に修正してください
getCategoryById.ts
import { MongoClient } from "https://deno.land/x/mongo@v0.31.1/mod.ts";
import { ObjectId } from "https://deno.land/x/mongo@v0.31.1/mod.ts";

interface CategorySchema {
  _id: ObjectId;
  name: string;
  visible: boolean;
  createdAt: number;
  updatedAt: number;
}

export default async function getCategoryById(_id: string): Promise<CategorySchema | null> {
  const client = new MongoClient();
  await client.connect("mongodb://localhost:27017");

  const db = client.database("example");
  const categories = db.collection<CategorySchema>("categories");

  const category = await categories.findOne({ _id: new ObjectId(_id) });

  client.close();

  return category ?? null;
}
それから、main関数を書くことで、この関数を呼び出して結果を表示します。
index.ts
import getCategoryById from "./getCategoryById.ts";

console.log(getCategoryById("6381c63b254d01ec76de9d34"));

動きそうなコードになった。また親切にindex.tsを書いてくれたのはありがたいんだけど、検索したい_id違うので改めて書いてもらうよう依頼する。

index.ts
-----
import { getUserById } from "./getUserById.ts";

const main = async () => {
    const user = await getUserById("6381c63b254d01ec76de9d34");
    console.log(user);
};

main();
-----

上記の index.tsに以下のように追記してください。

1. getCategoryByIdをインポートします
2. main関数にgetCategoryById()を実行して結果を表示するコードを追記します。getCategoryByIdに渡す引数は "6381c63b254d01ec76de9d38" です。
index.ts
import { getUserById } from "./getUserById.ts";
import { getCategoryById } from "./getCategoryById.ts";

const main = async () => {
    const user = await getUserById("6381c63b254d01ec76de9d34");
    console.log(user);
    const category = await getCategoryById("6381c63b254d01ec76de9d38");
    console.log(category);
};

main();

ここでimportエラーが発生した

あ、なるほどいつのまにか getCategoryById が export defaultされている。。これもう人間が直したほうが早いが一応責任をもってChatGPT先生に直してもらおうw

index.tsでimport errorが発生しました。
getCagegoryByIdのコード中の export default async function getCategoryById を export async function getCategoryById に更新してください
index.ts
import { getUserById } from "./getUserById.ts";
import { getCategoryById } from "./getCategoryById.ts";

const main = async () => {
const user = await getUserById("6381c63b254d01ec76de9d34");
console.log(user);
const category = await getCategoryById("6381c63b254d01ec76de9d38");
console.log(category);
};

main();

。。。あれ、いやそっちじゃなくてw どうもなんか変な思考ルートに入っちゃったみたいなので上記は手で直してエラーを解消することにする

getCagegoryById.ts.diff
- export default async function getCategoryById(_id: string): Promise<CategorySchema | null> {
+ export async function getCategoryById(_id: string): Promise<CategorySchema | null> {

これでindex.ts側のエラーはおさまった。

再度実行してみる

categories.json
{"_id":{"$oid":"6381c63b254d01ec76de9d36"},"name":"Sales","visible":true,"createdAt":1.669449275385E+12,"updatedAt":1.669449275385E+12}
{"_id":{"$oid":"6381c63b254d01ec76de9d37"},"name":"Marketing","visible":true,"createdAt":1.669452096358E+12,"updatedAt":1.669452096358E+12}
{"_id":{"$oid":"6381c63b254d01ec76de9d38"},"name":"Development","visible":true,"createdAt":1.669452096358E+12,"updatedAt":1.669452096358E+12}
{"_id":{"$oid":"6381c63b254d01ec76de9d39"},"name":"HR","visible":false,"createdAt":1.669452096358E+12,"updatedAt":1.669452096358E+12}
{"_id":{"$oid":"6381c63b254d01ec76de9d40"},"name":"Admin","visible":false,"createdAt":1.669452096358E+12,"updatedAt":1.669452096358E+12}
shell
docker cp ./categories.json mongo:/data/
docker exec -it mongo mongoimport --db example --collection categories --drop --file /data/categories.json
shell
deno run --allow-net index.ts
shell
{
  _id: new ObjectId("6381c63b254d01ec76de9d34"),
  no: 2,
  name: "Jiro Yamada",
  role: "staff",
  email: "jiro@example.com",
  createdAt: 1669452096358,
  updatedAt: 1669452096358
}
{
  _id: new ObjectId("6381c63b254d01ec76de9d38"),
  name: "Development",
  visible: true,
  createdAt: 1669452096358,
  updatedAt: 1669452096358
}

Worked fine!!

おわりに

最後の最後に妙なゾーンに入ってしまったChatGPT先生だったが、ほぼほぼ動くコードを書いてくれたのはやはり驚愕。まあdeno_mongoの古い書き方しかご存じなかった等、いろいろご指導差し上げた点はあったがそれは遠からず学習されることであろう。

そんなことよりペアプロ感が楽しい。「そっちかーw」とか「ああ、その手でいくか。」とか。思わず声に出た。正直これくらいのコードなら自分で書いたほうが早いが、もっと複雑な仕様になってくるとChatGPTが書くコードにはっとすることも出てくると思う。

そう考えるとChatGPT先生と一緒にプログラミングすることが楽しみになると思った。なんせ24時間好きなときに付き合ってくれるペアプログラマーなのがやばい。

全文掲載

Discussion