0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Go/GORM】データベース操作の基本:Model()とUpdates()の使い方

Posted at

こんにちは!フリーランスエンジニアのこたろうです。
GORMを使ったデータベース操作の基本について、学びで得た知見を共有します。

なぜGORMを使うの?

データベースを操作する方法は大きく分けて2つあります:

  1. 生のSQLを書く方法
-- 生のSQL
UPDATE users 
SET name = 'John', age = 25, updated_at = CURRENT_TIMESTAMP 
WHERE id = 1;
  1. ORMを使う方法(今回はGORM)
db.Model(&user).Updates(map[string]interface{}{
    "name": "John",
    "age":  25,
})

生のSQLを書く場合の問題点:

  • SQLインジェクションの危険性
  • タイプミスが起きやすい
  • カラム名の変更時に全SQLを修正する必要がある
  • プログラムとSQLが分離している

GORMを使うメリット:

  • SQLインジェクションの心配なし
  • コンパイル時にエラーを検出可能
  • モデルの変更が容易
  • Goのコードとして書ける

Model()メソッドの詳しい解説

Model()とは?

Model()は、どのテーブルのどのレコードを操作するかを指定するメソッドです。

基本的な使い方

// ユーザーのモデル定義
type User struct {
    ID        uint      `gorm:"primarykey"`
    Name      string
    Email     string
    Age       int
    CreatedAt time.Time
    UpdatedAt time.Time
}

// 使用例1:特定のレコードを指定
db.Model(&User{ID: 1})  // ID=1のユーザーを操作

// 使用例2:テーブル全体を指定
db.Model(&User{})  // usersテーブル全体を操作

Model()の動作の詳細

// 例:ユーザーの年齢を更新
user := User{ID: 1}
db.Model(&user).Update("age", 25)

この1行で行われていること:

  1. Model(&user)でusersテーブルを特定

    • 構造体名からテーブル名を推測(User → users)
    • IDフィールドから対象レコードを特定
  2. プライマリーキーの扱い

    • IDフィールドが自動的にWHERE句の条件になる
    • WHERE id = 1が自動生成される
  3. 更新時のタイムスタンプ

    • UpdatedAtフィールドがある場合、自動的に現在時刻が設定される

Updates()メソッドの詳細解説

更新方法の種類

  1. 構造体を使う方法:
user := User{
    Name: "John",    // 更新したい
    Age:  25,        // 更新したい
    Email: "",       // 空文字は無視される!
}
db.Model(&User{ID: 1}).Updates(user)
  1. mapを使う方法:
db.Model(&User{ID: 1}).Updates(map[string]interface{}{
    "name": "John",  // 更新される
    "age":  25,      // 更新される
    "email": "",     // 空文字でも更新される!
})

それぞれの特徴

構造体を使う場合:

  • メリット
    • タイプセーフ(コンパイル時にエラーを検出)
    • IDEの補完が効く
  • デメリット
    • zero値(0や空文字)は更新されない
    • 全フィールドが更新対象になる

mapを使う場合:

  • メリット
    • 必要なフィールドだけ更新可能
    • zero値でも更新可能
    • 動的に更新フィールドを決定可能
  • デメリット
    • タイプセーフではない
    • キーのタイプミスに気づきにくい

実践的なUpdates()の使用例

  1. フォームからの更新処理:
func updateUserFromForm(db *gorm.DB, userID uint, form UserForm) error {
    updates := map[string]interface{}{}
    
    // フォームの値が設定されている場合のみ更新
    if form.Name != "" {
        updates["name"] = form.Name
    }
    if form.Age > 0 {
        updates["age"] = form.Age
    }
    if form.Email != "" {
        updates["email"] = form.Email
    }
    
    // 更新実行
    result := db.Model(&User{ID: userID}).Updates(updates)
    if result.Error != nil {
        return fmt.Errorf("更新に失敗: %v", result.Error)
    }
    
    // 更新件数をチェック
    if result.RowsAffected == 0 {
        return fmt.Errorf("ユーザーID %d は存在しません", userID)
    }
    
    return nil
}
  1. 条件付き一括更新:
// 20歳未満のユーザーのステータスを一括更新
result := db.Model(&User{}).
    Where("age < ?", 20).
    Updates(map[string]interface{}{
        "status": "junior",
        "rank":   1,
    })

// 更新件数を確認
fmt.Printf("%d件のレコードを更新しました\n", result.RowsAffected)

テストにおけるsqlmock.AnyArg()の詳細

sqlmock.AnyArg()とは?

テスト時に「値の存在は確認するが、具体的な値は問わない」場合に使用する特別な値です。

使用が推奨されるケース

  1. タイムスタンプフィールド(常に変化する値)
  2. UUIDやランダムな値
  3. 具体的な値が重要でない場合

実践的なテストコード

func TestUpdateUser(t *testing.T) {
    // モックDBのセットアップ
    db, mock := setupMockDB(t)
    
    // テストケース
    user := &User{
        ID:   1,
        Name: "John",
        Age:  25,
    }
    
    // SQLの期待値を設定
    mock.ExpectExec(`UPDATE "users" SET`).
        // name, age, updated_atの順で引数を検証
        WithArgs(
            user.Name,           // nameは具体的な値をチェック
            user.Age,            // ageも具体的な値をチェック
            sqlmock.AnyArg(),    // updated_atは任意の値を許容
        ).
        WillReturnResult(sqlmock.NewResult(1, 1))
    
    // テスト実行
    err := updateUser(db, user)
    
    // 検証
    assert.NoError(t, err)
    assert.NoError(t, mock.ExpectationsWereMet())
}

このテストコードの解説:

  1. ExpectExecでSQLパターンを指定
  2. WithArgsで引数の検証ルールを設定
  3. sqlmock.AnyArg()で「任意の値」を許容
  4. WillReturnResultで期待する実行結果を設定
  5. ExpectationsWereMetで全ての期待値が満たされたか確認

GORMを使う際のベストプラクティス

  1. モデルの定義

    • 適切なタグを使用
    • 必要なフィールドを明確に
    • 命名規則の統一
  2. 更新操作

    • 可能な限り構造体を使用
    • 動的更新が必要な場合のみmapを使用
    • 更新結果の確認を忘れずに
  3. テスト

    • sqlmockを活用
    • 適切な期待値の設定
    • エッジケースの考慮

まとめ

  • Model()でテーブルと対象レコードを特定
  • Updates()で効率的な更新処理
  • map[string]interface{}で柔軟な更新
  • テストではsqlmock.AnyArg()を活用
  • 適切な使い分けで保守性の高いコードに

これらの機能を理解し使いこなすことで、より堅牢なデータベース処理を実装できます。

0
0
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
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?