Skip to content

Latest commit

 

History

History
405 lines (230 loc) · 7.8 KB

phase-5-6.md

File metadata and controls

405 lines (230 loc) · 7.8 KB

Phase 5.6: 永続化(Create / Save / Update / Delete)をTDDで実装する

← 前へ: Phase 5.5 | 目次 | 次へ: Phase 5.7 →

Phase 5.1のSQLインジェクション対策は、このフェーズでも必須です。INSERT・UPDATEの属性値とUPDATE・DELETEのIDをバインドし、動的な属性名は許可リストで制限します。引用符やSQL断片を含む入力を使い、意図した値だけが保存され、対象外の行が更新・削除されないテストを提出してください。

概要

実行前に共通セットアップと各Phaseの確認手順を確認してください。演習コードはwork/で前のPhaseから引き継ぎます。

このフェーズでは、これまでに構築してきたModelに対して、データの永続化(保存・更新・削除)機能をTDDで実装します。

Phase 5.5までで、あなたはすでに以下を実現しています:

  • DBからデータを取得できる(all / find / where)
  • DBの1行をオブジェクトとして扱える

しかし、まだできていないことがあります:

  • 新しいデータを保存する
  • 既存のデータを更新する
  • データを削除する

このフェーズではそれらを実装します。


このフェーズの目的

以下を説明できる状態になること:

  • insert と update の違い
  • オブジェクトとDBの状態の違い
  • なぜ save というメソッドが必要になるのか
  • ORMにおける「状態管理」の難しさ

到達目標

  • 新規レコードを作成できる
  • 既存レコードを更新できる
  • レコードを削除できる
  • save が insert / update を切り替えられる
  • オブジェクトの状態を元に処理が分岐できる

前提

  • Phase 5.5(Finder / Query 共通化)完了
  • User.all, User.find, User.where が動作する

重要ルール

1. 必ずテストから書く(RED)

2. 最小実装で通す(GREEN)

3. 状態をテストで固定する(REFACTOR)


このフェーズで目指す最終形(イメージ)

user = User.new(name: "Alice", email: "alice@example.com")
user.save

user.name = "Alice Updated"
user.save

user.destroy

※ ただし最初からこれを作らないこと ※ 必ず小さなステップで進めること


演習課題


Step 1: 新規レコードを作成したいというテストを書く(RED)

まずは「新しいデータを保存したい」という要求を固定します。

テストの方向性

  • User.create(...) または User.new + save
  • DBにデータが追加される

問い

  • どちらのAPIから作るか?

    • create
    • new + save

👉 どちらでもOK(テストで固定する)

👉 RED


Step 2: INSERTを最小実装する(GREEN)

やること

  • SQLの INSERT を実行する
  • DBにレコードが追加されることを確認する

観察

  • IDはどうなるか?
  • オブジェクトに反映されているか?

Step 3: オブジェクトとDBのズレに気づく

観察

  • DBには保存された
  • でもオブジェクトはどうなっている?

気づき

「保存されたかどうか」がオブジェクトに反映されていない


Step 4: save を使いたいというテストを書く(RED)

ここで初めて save の必要性が出ます。

テストの方向性

  • User.new(...).save ができる
  • DBに保存される

👉 RED


Step 5: save(INSERT)を実装する(GREEN)

やること

  • 新規オブジェクトをINSERTする

問い

  • どの時点で「新規」と判断するか?

Step 6: 既存レコードを更新したいというテストを書く(RED)

次に更新処理です。

テストの方向性

user = User.find(1)
user.name = "Updated"
user.save
  • DBが更新されていること

👉 RED


Step 7: UPDATEを最小実装する(GREEN)

やること

  • UPDATE SQLを書く

問い

  • どのレコードを更新するか?
  • idはどこから取得するか?

Step 8: saveが2つの責務を持つことに気づく

ここで重要な気づき:

  • saveはINSERTもUPDATEもする
  • どちらをやるか判断が必要

問い

  • 新規か既存かどうやって判定するか?

Step 9: 状態判定のテストを書く(RED)

テストの方向性

  • 新規オブジェクト → INSERT
  • 既存オブジェクト → UPDATE

👉 RED


Step 10: 状態による分岐を実装する(GREEN)

やること

  • 内部状態に応じて処理を切り替える

ヒント

  • idの有無
  • フラグ

Step 11: destroy を使いたいというテストを書く(RED)

テストの方向性

user = User.find(1)
user.destroy
  • DBから削除される

👉 RED


Step 12: DELETEを実装する(GREEN)

やること

  • DELETE FROM ... を実装

Step 13: オブジェクト状態の扱いを考える

問い

  • destroy後のオブジェクトはどうなる?
  • save後のオブジェクトはどうなる?

気づき

「オブジェクトはDBの完全な写像ではない」


Step 14: 最小限の整理を行う(REFACTOR)

やること

  • insert / update の共通部分整理
  • 属性の取得方法整理

注意

  • 過剰な抽象化は禁止

Step 15: 「永続化とは何か」を言語化する

必ず説明すること

  • saveは何をしているのか
  • insertとupdateの違い
  • なぜ状態管理が必要か
  • ORMの難しさはどこにあるか

発展課題


課題1: saveの戻り値

  • true / false にするか?
  • 何を意味するか?

課題2: update専用メソッドを作る

user.update(name: "...")

課題3: 変更がない場合のsave

  • SQLを発行するか?
  • 無駄ではないか?

課題4: バリデーションを考える(次フェーズへの伏線)


提出物


1. テストコード

  • create / save / update / destroy
  • 状態による分岐

2. 実装コード

  • Modelの永続化処理

3. 設計メモ(重要)

以下を説明:

  • saveは何をしているか
  • なぜ状態管理が必要か
  • ORMの難しさはどこか
  • 今の実装の限界は何か

レビュー観点

  • TDDが守られているか
  • insert / update が正しく分離されているか
  • 状態判定が明確か
  • 責務が崩れていないか
  • 過剰設計になっていないか

よくある失敗


❌ saveを魔法にする

→ 内部の分岐を理解することが重要


❌ 状態を曖昧にする

→ バグの温床


❌ SQLをコピペで増やす

→ 共通化できる部分を見極める


❌ いきなりActiveRecordを真似する

→ 理解が浅くなる


このフェーズの本質

このフェーズで得るべき理解は:

ORMの難しさは「取得」ではなく「状態を持つオブジェクトをDBと同期すること」にある


次フェーズへの接続

ここまで来ると、次の違和感が出ます:

  • 不正データも保存できてしまう
  • ルールがどこにもない
  • ドメイン知識がModelにない

次のフェーズでは:

👉 Phase 5.7: バリデーション / モデル責務


← 前へ: Phase 5.5 | 目次 | 次へ: Phase 5.7 →