LoginSignup
0
1

More than 5 years have passed since last update.

【gorm】データの登録・更新・論理削除時にユーザIDを自動で登録・更新する

Last updated at Posted at 2018-08-12

はじめに

GORMを通して登録・更新・論理削除すると、登録日時・更新日時・削除日時を自動的に登録・更新してくれます。

一方、今開発中のシステムでは登録・更新・削除を実施したユーザのIDも保持する仕様としているため、日時と同様に自動で登録・更新してくれると便利だなと思っていたのでやってみました。

方針

ユーザ情報は構造体に持たせておく形とします。
以下の例でいうと、RequestUserにユーザ情報を入れた状態で登録・更新・削除を実施すると、自動的にCreateUserIDUpdateUserIDDeleteUserIDにユーザIDがセットされ、登録・更新・削除されるようにします。

type User struct {
    ID int
    // ... 何かしらのデータ
}

type TestData struct {
    ID           int
    // ... 何かしらのデータ
    CreatedAt    time.Time
    CreateUserID int
    UpdatedAt    time.Time
    UpdateUserID int
    DeletedAt    mysql.NullTime
    DeleteUserID sql.NullInt64
    RequestUser  User
}

また、GORMには登録・更新・削除の前後にコールバックを挟むことができるため、登録・更新・削除前にコールバックを使用して実装します。

やり方

まずはコールバックを登録するところから。登録・更新・削除前にコールバックを実行したいので以下の通りになります。

// ConnectDB DBに接続する
func ConnectDB(databaseName string) *gorm.DB {

    db, err = gorm.Open("mysql", "...")

    db.Callback().Create().Before("gorm:create").Register("create_user", createUserCallback)
    db.Callback().Update().Before("gorm:update").Register("update_user", updateUserCallback)
    db.Callback().Delete().Before("gorm:delete").Register("delete_user", deleteUserCallback)

    return db
}

次に、登録・更新のコールバックをみていきます。
下記の条件を満たす場合に、IDフィールドからデータを取得し、CreateUserIDUpdateUserIDにセットしています。

  • 更新対象の構造体にRequestUserの構造体がある
  • 構造体の中にIDというint型のフィールドがある

func createUserCallback(scope *gorm.Scope) {

    u, ok := scope.FieldByName("RequestUser")
    if ok && u.Field.Type().Kind() == reflect.Struct {
        v := u.Field.FieldByName("ID")
        if v.IsValid() && v.Type().Kind() == reflect.Int {

            userID := v.Int()

            if createUserIDField, ok := scope.FieldByName("CreateUserID"); ok {
                createUserIDField.Set(userID)
            }

            if updateUserIDField, ok := scope.FieldByName("UpdateUserID"); ok {
                updateUserIDField.Set(userID)
            }
        }
    }
}

func updateUserCallback(scope *gorm.Scope) {

    u, ok := scope.FieldByName("RequestUser")
    if ok && u.Field.Type().Kind() == reflect.Struct {
        v := u.Field.FieldByName("ID")
        if v.IsValid() && v.Type().Kind() == reflect.Int {

            userID := v.Int()

            if updateUserIDField, ok := scope.FieldByName("UpdateUserID"); ok {
                updateUserIDField.Set(userID)
            }
        }
    }
}

問題は削除の場合です。
登録・更新の場合と違って、構造体内のDeleteUserIDを事前に変更しても論理削除のUpdateには反映されないため、論理削除のSQLとは別のSQLを実行します。(2回SQLが実行されるのであまり褒められたものではありませんが…)
以下実装例です。

func deleteUserCallback(scope *gorm.Scope) {

    if !scope.HasError() && !scope.Search.Unscoped {
        u, ok := scope.FieldByName("RequestUser")
        if ok && u.Field.Type().Kind() == reflect.Struct {
            v := u.Field.FieldByName("ID")
            if v.IsValid() && v.Type().Kind() == reflect.Int {

                userID := v.Int()

                if deleteUserIDField, ok := scope.FieldByName("DeleteUserID"); ok {

                    var extraOption string
                    if str, ok := scope.Get("gorm:delete_option"); ok {
                        extraOption = fmt.Sprint(str)
                    }

                    scope.Raw(fmt.Sprintf(
                        "UPDATE %v SET %v=%v%v%v",
                        scope.QuotedTableName(),
                        scope.Quote(deleteUserIDField.DBName),
                        scope.AddToVars(userID),
                        addExtraSpaceIfExist(scope.CombinedConditionSql()),
                        addExtraSpaceIfExist(extraOption),
                    )).Exec()
                    scope.SQLVars = []interface{}{}
                }
            }
        }
    }
}

下記の条件を満たす場合に、IDフィールドからデータを取得し、DeleteUserIDにセットし、更新を実行しています。

  • 更新対象の構造体にRequestUserの構造体がある
  • 構造体の中にIDというint型のフィールドがある

なお、更新の実装は実際に論理削除を行っているソースを参考にしました。

ゴリ押しになってしまいましたが、これで削除時にDeleteUserIDを自動で更新することもできました。

終わりに

これで、何も考えずに RequestUserにデータを入れておけば、登録・更新・削除時にうまいことユーザIDを更新してくれるようになりました。楽チンです。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1