
この記事はテックタッチアドベントカレンダー 17 日目の記事です。
SRE チームの taisa です。韓国ドラマの「スタートアップ」が最近 Netflix で最終話までみれるようになりましたね。最近はぼちぼち来年の抱負を考えるようになりました。懲りずにまた英語をやろうかと考え中です。
今回は「GORM v2 触ってみた Major Features 編」の続きで「Breaking Changes 編」です。
GORM v2 リリースノート
1 系から移行する場合は少なくともこちらにある内容の対応をする必要があります。
Breaking Changes
1 系からの変更で影響が大きそうな変更内容を確認します。
- Tags
- Soft Delete
- BlockGlobalUpdate
- ErrRecordNotFound
- Updating with struct
- Migrator
- Count
- Transaction
順に見ていきます。
Tags
auto_increment などのタグ名は camelCase 推奨で snake_case は使えなくなります。確認したところ現時点では snake_case でも動作しました。
type User struct { ID int `gorm:"primaryKey"` // camelCase とする CompanyID int Company Company Name string Address string Age int }
Soft Delete
DeletedAtはgorm.DeletedAtを指定するようになりました。かわりに、gorm.DeletedAtを指定していればカラム名がDeletedAtでなくても Soft Delete となります。確認したところ*time.Time と指定しても Soft Delete とならなかったので該当箇所はすべて書き換える必要があります。
type User struct { ID uint DeletedAt gorm.DeletedAt } type User struct { ID uint // 違うカラム名でもOK Deleted gorm.DeletedAt }
BlockGlobalUpdate
BlockGlobalUpdateモードがデフォルトとなり、DELETE FROM usersのような Where 句の指定がない場合は SQL が実行されません。実行したい場合は意図的にAllowGlobalUpdateモードにする必要があります。意図しない更新が防げるのでうれしいアップデート。
// Where 句の指定があれば実行可能 db.Where("name = ?", "username").Delete(&User{}) // 実行されない db.Raw("delete from users") // `AllowGlobalUpdate: true`を指定すると実行可能 db.Session(&gorm.Session{AllowGlobalUpdate: true}).Delete(&User{})
ErrRecordNotFound
RecordNotFoundやIsRecordNotFoundError関数がなくなったので以下のような書き方でチェックする必要があります。また、First、Last、Takeを利用したときにErrRecordNotFoundエラーが返る可能性があります。
err := db.First(&user).Error if errors.Is(err, gorm.ErrRecordNotFound) { return gorm.ErrRecordNotFound }
v1 で以下のような書き方にて Not Found チェックしている場合は変更する必要があります。
result := db.First(&user) if result.RecordNotFound() { return gorm.ErrRecordNotFound }
err = db.First(&user).Error if gorm.IsRecordNotFoundError(err) { return gorm.ErrRecordNotFound }
Updating with struct
構造体やマップを使って更新する際に、Selectで指定したカラムだけを更新できるようになりました。また、ゼロバリュー(0 や空)の値でもその値に更新できます。ドキュメントの例は構造体でしたが、構造体を使うとPanicが発生して原因の特定はできませんでした(ドキュメントのUpdateはUpdatesの誤りだと思われます)。
db.Model(&model.User{}).
Select("Age").
Where("name = ?", "username").
Updates(map[string]interface{}{"Name": "hello", "Age": 0, "Address": ""})
// UPDATE `users` SET `age`=0 WHERE name = 'username'
マップを使って Where 句にゼロバリューを入れてみても値が消えず、指定した値で検索できました。
user := model.User{}
db.Model(&model.User{}).
Where(map[string]interface{}{"Age": 0}).First(&user)
// SELECT * FROM `users` WHERE `Age` = 0 ORDER BY `users`.`id` LIMIT 1
Migrator
Migrator はいくつかの変更がありますが、一番影響ありそうな変更はデフォルトで外部キーがはられるようになったことです。
- Migrator will create database foreign keys by default
GORM 2.0 Release Note | GORM - The fantastic ORM library for Golang, aims to be developer friendly.
Count
地味ですが、Count の引数は*int64だけとなりました。これまでほかの型を使っている場合は変更する必要があります。
Transactions
Transactionを使うことが推奨となり、RollbackUnlessCommittedなどいくつかのドランザクション関数が廃止になりました。これも利用している箇所があれば書き換える必要があります。
以上、影響の 1 系から 2 系にアップデートするにあたり影響の大きそうな変更の紹介でした。ほかにもいくつか取り上げていない変更があるのでリリースノートや公式ドキュメントを参考にしてみてください。
まとめ
2 回に渡って(Prometheus を入れると 3 回)GORM v2 の変更点を実際に動かしながら確認していきました。すごく魅力的な機能がある一方でサンプルコードに誤りがあったり動かなかったり、移行するにしてももう少し安定してからの方が良さそうだな、という印象を受けました。ドキュメントなど今回触ってみて気になった箇所は PR を出して少しでも貢献しようと思います。
次回は、kosy による「(仮)Material-ui を使ってドラッグ&ドロップで並べ替えできる Table を作る」です!よろしくです!