June 17, 2021

gormでdeleted_atを含むフィールドを扱う時の注意点

最近、仕事でGoを触ることが多いです。

GORMを使っているプロジェクトで面白いバグを発見したのでシェアします。
ミスる人が多そうなバグだなとも思いました。

はじめに

GORMでDBとマッピングするための構造体を書く際、下記のようになると思いますが、これは明らかに間違っています。
どこでしょうか?

type Person struct {
  Name      string `json:"name"`
  Age       uint `json:"age"`
  DeletedAt time.Time `json:"deleted_at"`
}

正解は、DeletedAttime.Time型になっていることです。
なぜこの実装がまずいのか説明します。

解説

先に正解を示すと下記です。

type Person struct {
  Name      string `json:"name"`
  DeletedAt gorm.DeletedAt `json:"deleted_at"`
}

DeletedAt は、Nullableな属性です。(論理削除されたタイミングで初めて値が入る)。
ですが、time.Time 型を使っていると、テーブルに保存されるタイミングで 0000-00-00 00:00 という値が入ってしまい、Nullでなくなってしまいます。(重要)

「削除されていない列を取得したい」というケースでは、往々にして
select * from <table name> where deleted_at is null;
のようなSQLが実行されますが、上記の問題が発生するとこのSQLは機能しません。

そこで、gorm.DeletedAt を使うことで保存時に上記の問題が発生することを防ぐことができ、Nullが入ったまま保存が可能となり、問題は解消します。

ちなみに、gorm.DeletedAtsql.NullTime のDefined type(日本語でどう表現するのかわからない)です。
どっちを使ってもいいですが、個人的には前者の方が分かりやすいと思います。
https://github.com/go-gorm/gorm/blob/810058cd55e8a92f031b5ce3c0e5b7918911b3f3/soft_delete.go#L13

created_at や updated_at はNULLになることがないため、time.Time でも大丈夫だと思いますが、
deleted_at も同じノリでやっちゃうと罠にはまるねというお話しでした。

© yuta-ron 2023

Powered by Hugo & Kiss.