解答 04.3 — DAO層(TaskDAO.MarkDone)¶
このノートブックは 04.3-three-layer-mvc-dao.ipynb の練習問題の解答です。
まず問題の前提コードを再掲し、次に解答、最後にチェックを実行します。
先に自分の力で解いてから、答え合わせに使ってください。
前提コード(問題ノートブックと同じ定義)¶
In [1]:
import (
"database/sql"
"errors"
"fmt"
_ "modernc.org/sqlite"
)
// ErrUnanswered は、練習問題が未回答のときにプレースホルダ関数が返す特別なエラー。
var ErrUnanswered = errors.New("未回答: この関数はまだ実装されていません")
func openCourseDB(path string) *sql.DB {
db, err := sql.Open("sqlite", path)
if err != nil {
panic(err)
}
db.SetMaxOpenConns(1)
return db
}
const dbPath = "file:_04_3_three_layer_solutions.db"
type Task struct {
ID int64 `json:"id"`
Title string `json:"title"`
Done bool `json:"done"`
}
type TaskDAO struct{}
func (TaskDAO) Create(db *sql.DB, title string) (int64, error) {
res, err := db.Exec(`INSERT INTO tasks (title, done) VALUES (?, 0)`, title)
if err != nil {
return 0, err
}
return res.LastInsertId()
}
func (TaskDAO) GetByID(db *sql.DB, id int64) (Task, error) {
var t Task
err := db.QueryRow(`SELECT id, title, done FROM tasks WHERE id = ?`, id).Scan(&t.ID, &t.Title, &t.Done)
return t, err
}
In [2]:
%%
db := openCourseDB(dbPath)
defer db.Close()
db.Exec(`DROP TABLE IF EXISTS tasks`)
db.Exec(`CREATE TABLE tasks (id INTEGER PRIMARY KEY, title TEXT, done INTEGER)`)
fmt.Println("✅ tasks テーブルを初期化しました")
✅ tasks テーブルを初期化しました
解答: MarkDone¶
対象行が無くても db.Exec 自体はエラーになりません(sql.Result.RowsAffected() を見なければ
0件更新かどうかは分かりませんが、この仕様では確認不要です)。
In [3]:
func (TaskDAO) MarkDone(db *sql.DB, id int64) error {
_, err := db.Exec(`UPDATE tasks SET done = 1 WHERE id = ?`, id)
return err
}
チェック(問題ノートブックと同じ期待値)¶
答え合わせ用のヘルパー mustEqual を定義します。
In [4]:
import "reflect"
func mustEqual(got, want any, name string) {
if reflect.DeepEqual(got, want) {
fmt.Printf("✅ Passed: %s\n", name)
return
}
panic(fmt.Sprintf("❌ %s\n got = %v (%T)\n want = %v (%T)", name, got, got, want, want))
}
In [5]:
%%
db := openCourseDB(dbPath)
defer db.Close()
dao := TaskDAO{}
id, createErr := dao.Create(db, "牛乳を飲む")
if createErr != nil {
panic(createErr)
}
err := dao.MarkDone(db, id)
mustEqual(err, nil, "MarkDone はエラーなし")
got, getErr := dao.GetByID(db, id)
mustEqual(getErr, nil, "GetByID はエラーなし")
mustEqual(got.Done, true, "MarkDone 後は done=true")
fmt.Println("🎉 すべてのチェックが通りました")
✅ Passed: MarkDone はエラーなし
✅ Passed: GetByID はエラーなし ✅ Passed: MarkDone 後は done=true 🎉 すべてのチェックが通りました
解説¶
- プレースホルダで安全に更新:
WHERE id = ?により、idの値がどんな内容でも SQL構造には影響しません(04.4で学ぶSQLインジェクション対策と同じ考え方です) - 対象行が無くてもエラーにしない: UPDATE文自体は「0行更新」で正常終了するため、
「そのIDが存在するか」を厳密にチェックしたい場合は別途
RowsAffected()を確認する必要が あります(このレッスンの仕様では省略しています)