【TypeScript】Prisma構文内のキー名に変数を、CRUDを使いまわすぜ 

## 導入
初めまして、新卒のエンジニアです。
NestJSを使用して個人開発を進めています。
僕は処理をスマートに書くことができない、雑魚エンジニアです。
それを何とかしようと、以下の本を読んだところ、非常に感銘を受けました。

↓amazonのリンクです。
[良いコード/悪いコードで学ぶ設計入門 ―保守しやすい 成長し続けるコードの書き方](https://amzn.asia/d/ajgGofH)

クラス構造やクラス間の関係性など、なかなかイメージに落とし込むことが難しい範囲を、具体ベースに教えてくれています。これぞ良書！

それから、効率的なコードを書くことに対してモチベーションが爆上がりしています。
TypeScriptで書いたロジックを共通化してみたため、それを共有します。

ただ共通化してみただけですが、Prismaの学習にもなります。
Prisma構文内のキーに変数を用いています。

## 似た3つのtoggleロジック
まず、本記事で共通化するロジックは以下の機能です。
1. いいね機能
1. ストック機能
1. フォロー機能

他ユーザが投稿したコンテンツに対していいね、ストックする機能と他ユーザをフォローする機能です。
これらの機能には共通点があります。

**それは、新しい場合はcreate、重複した場合はdeleteするということです。**

例えば、X(旧twitter)を想像してください。
あるツイートをいいねしてみたとします。また再度、そのツイートのいいねボタンを押すと、いいねが外れます。
この処理を表しています。
いいねもストックも同様です。

**これを本記事ではtoggleロジックと表現していきます。**
これら3つのロジックを共通化していきます。

## 解決策
ここからはコードベースで記していきます。
### クラス構造
#### like.service.ts
コントローラがユーザからのリクエストを受け取り、サービスはコントローラからリクエスト情報を受け取ります。
```
import { Injectable, Request } from "@nestjs/common";
import { ToggleEvent } from "src/domain/domain-service/toggle.event";

@Injectable({})
export class LikeService {
    constructor(private readonly toggleEvent: ToggleEvent
    ) { }

    private readonly likeModel: string = 'like';
    private readonly activeKey: string = 'stamper_id';
    private readonly passiveKey: string = 'content_id';
    private readonly uniqueKey: string = 'unique_stamper_match_review';

    async toggleLikeState(@Request() req, insert_content_id: bigint) {
        try {
            const userId = req.user.userId;
            return this.toggleEvent.actToggle(this.likeModel, this.activeKey, this.passiveKey, this.uniqueKey, userId, insert_content_id);
        } catch (error) {
            console.error('toggle like state :', error);
            throw error;
        }
    }
}
```

これは余談ですが、NestJSはモジュールで依存関係を管理しています。
ToggleEventクラスはモジュールで読み込む必要があるため、注意してください。
他のstock, followのサービスクラスも同じ構造です。

渡す引数が多いですが、このような意味があります。
<dl>
    <dt>this.likeModel</dt>
    <dd>Prisma ORMのモデル名です。likesテーブルとマッピングされています。</dd>
    <dt>this.activeKey</dt>
    <dd>実行したユーザIDのカラム名です。他のstock、followとはカラム名が異なるため、能動的な意味を込めて、activekeyと命名しています。あくまでカラム名です。</dd>
    <dt>this.passivekey</dt>
    <dd>activeKeyの逆です。受動的な意味を込めています。いいねやストックはされたコンテンツID、フォローはフォローされたユーザIDです。あくまでカラム名です。</dd>
    <dt>this.uniqueKey</dt>
    <dd>テーブルにあるユニークキー名です。2つのカラム複合でユニークキーがあります。このキー名がそれぞれ異なるため、引数で渡しています。</dd>
    <dt>userId</dt>
    <dd>ユーザIDです。this.activeKeyカラムに挿入されます。</dd>
    <dt>passiveId</dt>
    <dd>受動側のIDです。コンテンツIDやフォローされたユーザIDなどです。this.passiveKeyカラムに挿入されます。</dd>
</dl>

### toggle.event.ts
それでは共通化部分です。

```
import { Injectable } from "@nestjs/common";
import { PrismaService } from "src/infrastructure/prisma/prisma.service";

@Injectable({})
export class ToggleEvent {
    constructor(private readonly prisma: PrismaService) {}

    async checkExists(insertDB: string, activeKey: string, passiveKey: string, uniqueKey: string, userId: bigint, passiveId: bigint) {
        const existedBool = await this.prisma[insertDB].findUnique({
            where: {
                [uniqueKey]: {
                    [activeKey]: userId,
                    [passiveKey]: passiveId
                }
            }
        });

        if (existedBool) {
            await this.prisma[insertDB].delete({
                where: {
                    [uniqueKey]: {
                        [activeKey]: userId,
                        [passiveKey]: passiveId
                    }
                }
            });
            return { message: `${uniqueKey} removed` };
        } else {
            await this.prisma[insertDB].create({
                data: {    
                    [activeKey]: userId,
                    [passiveKey]: passiveId
                }
            });
            return { message: `${uniqueKey} added` };
        }
    }
}
```

渡されたイベントごとに、テーブルに同値が存在するか判別、生成または削除を行います。

以下は重複するカラムが存在するか、という箇所です
```
const existedBool = await this.prisma[insertDB].findUnique({
    where: {
        [uniqueKey]: {
            [activeKey]: userId,
            [passiveKey]: passiveId
        }
    }
});
```

1行目の`[insertDB]`は本当ならこういう書き方をします
```
const existedBool = await this.prisma.like.findUnique({
```
ですが、今回はロジックの共通化です。likeだけではなく、stockもfollowも実行する必要があります。そのため、`this.prisma[insertDB]`と変数を入れています。


次に注目してほしいのはprisma構文内の`[uniqueKey]`です。
prismaは大変良くできており、マイグレーション時点でそれぞれのモデルごとに、メソッドごとの相応しい型付けを勝手に行ってくれます。
likesテーブルのユニークキーは`unique_stamper_content`という名前です。
`stamper_id`と`content_id`というカラムがあります。
likeモデルのfinduniqueメソッドでは、以上のキーが必要です。
ですがこれらのキー名は、like、stock、followで異なります。
そこで、各サービスから固有値をキー名で渡し、それを変数としてprisma構文内のキーで呼び出しています。

キー名を変数で、よくできた言語ですねえ。

## 改善点
サービスファイルから渡す引数が多いことが気になります。
脳筋感が満載です。
次はこれを何とかします。

また、本記事で取り上げたコードはNestJS内で書いています。
NestJSが好きな方はいいね、コメントしてくれると嬉しいです！
同志よ、立ち上がれ 。