sequelize-typescriptを使っていて、自動で更新者IDを設定する仕組みをSequelizeのフックを使って実現しようとしたんですが、upsertだけ上手くいかなかったのでメモを残しておきます。
やろうとしたこと
createやupdate、upsert等の更新系のメソッド呼び出しでリクエスト中のユーザー情報を渡して、フック内でカラムを自動設定したい
const user: User = { id: 1 };
this.model.upsert(data, { user });
なお、以下のように型宣言してあげるとupsertの第2引数に好きな型を渡せるようになります。
この第2引数の値はフック側で受け取れるようになるので、必要に応じて拡張します。
declare module 'sequelize' {
export interface UpsertOptions {
user?: User;
}
}
現象
createやupdate、bulkCreateはbefore~フックで変更した内容がDBにちゃんと反映されるのにupsertだけDBに反映されない
調査
beforeUpsertの中で値を更新しても実際のカラムは更新されないみたいで、かなり昔からある問題みたいです。。。
(2018年に作成されたIssueが2023年9月時点でいまだにOpenのままです)
参考:https://github.com/sequelize/sequelize/issues/8829
@BeforeUpsert
onBeforeUpsert(values: any, options: UpsertOptions) {
// ここで値を更新しても実際のカラムには反映されない
values.createUserId = options.user.id;
}
補足:beforeUpsertに渡されるオブジェクトはモデルクラスではなくupsert呼び出し時の更新オブジェクトらしい
beforeCreateやbeforeUpdate、beforeBulkCreate等のフックでは更新対象のモデルクラスがvaluesに渡されるんですが、upsertはなぜか更新用オブジェクトがそのまま渡されるそうです。。。
以下でコメントされてました
https://github.com/sequelize/sequelize/issues/8829#issuecomment-355338534
const data = { key: value };
this.model.upsert(data);
@BeforeUpsert
onBeforeUpsert(values: any, options: UpsertOptions) {
// ここで渡されるvaluesは上記upsertの第一引数に渡したオブジェクトが参照渡しされる
// valuesを編集するとsample.ts型のdataの値が変わります
}
回避策①:updateに分ける
更新対象データから追加用と更新用にデータを分けて、bulkCreateとupdateそれぞれでクエリを実行する
const arryForAdd = [];
const arryForUpd = [];
for (const d of data) {
if (d.id) {
// idがあるデータを更新対象と判断する
arryForUpd.push(d);
} else {
arryForAdd.push(d);
}
}
return this.sequelize.transaction(async (transaction) => {
if (arryForAdd.length) {
await this.model.bulkCreate(arryForAdd, { transaction });
}
if (arryForAdd.length) {
await this.model.update(arryForAdd, { transaction });
}
}
回避策②:bulkCreateを使う
bulkCreateのupdateOnDuplicateオプションを使うことでupsertと同じような挙動を実現できます。
beforeBulkCreateはちゃんとデータ更新されます。
ただ、更新対象のカラムを文字列で指定しないといけないので、メンテナンス性は微妙。。。(カラムの追加や名称変更時に見逃しやすい)
this.model.bulkCreate(data, {
updateOnDuplicate: [
'createUserId',
・・・
]
});
参考:https://sequelize.org/api/v6/class/src/model.js~model#static-method-bulkCreate