ハコヅミを作る #61:プランを足して使えるようにする(試したら、全員のチェックが消えるマイグレーションだった)

前回(#60)の続きです。今シーズン最後のキャンプに出かける前に、来年のための機能を1つ作りました。

  • 組み合わせの数だけ、プランが要る
  • 足し引きしても、チェックを残す
  • 列を1つなくすだけ、のはずだった
  • プランの欄の形を、2回変える
  • チェックを付けると、欄が閉じる
  • 3回続けて、書き忘れる

組み合わせの数だけ、プランが要る

キャンプに、ロードバイクを積んでいくことがあります。キャンプ場のまわりを走るためです。ライト、空気入れ、ヘルメット。自転車の荷物も、忘れたくありません。

ハコヅミでは、キャンプを登録するときに「プラン」を1つ選びます。プランは、持っていくギアの組み合わせです。ソロ、デュオ、ファミリー。選んだプランの中身が、持ち物チェックに並びます。

ここに自転車を足そうとすると、こうなります。

  • ソロ
  • ソロ+自転車
  • ファミリー
  • ファミリー+自転車

組み合わせの数だけ、プランが要ります。ライトを1つ買い足したら、自転車入りのプランを全部直すことになります。

これは、ハコヅミを作る前に Notion で困っていたことと、同じ形です。Notion では、「人数」と「泊まりか日帰りか」の2つの軸で、持ち物を管理していました。軸が増えるたびに、組み合わせが掛け算で増えます。

そこで、プランを足して使えるようにします。「ソロ」と「自転車」を、両方選ぶ。自転車の荷物を直すのは、「自転車」のプランの1か所で済みます。

キャンプの画面の見出し。1行目に期間「2026/10/17(土)〜10/18(日)・1泊」、2行目に「ソロ / 自転車」と、進み具合「2 / 6」

プランを2つ選んだキャンプ(架空のデータ)

実戦で試せるのは、春

これまでは、「使ってみてから決める」を大事にしてきました。ところが、今回はそれができません。今シーズンのキャンプは、これで終わりです。次に自転車を積むのは、春になります。

待っても、決める材料は増えません。机の上で決めて、冬のあいだに作っておくことにしました。春の最初のキャンプで、そのまま実戦に使えます。

「使ってから決める」は、使える見込みがあるときの話です。しばらく使えないと分かっているなら、考えが新しいうちに決めてしまうほうが、あとで楽です。


足し引きしても、チェックを残す

プランをいくつも選べるようにすると、今までの決まりと、ぶつかるところがあります。

今までは、キャンプのプランを変えると、持ち物チェックを最初から作り直して、チェックを全部外していました。ソロからファミリーに変えたなら、荷物は大きく変わります。「最初から見直してください」という考えです。

ところが、プランを足せるようになると、こういう場面が出ます。

前の晩に、荷物を半分積んだ。やっぱり、自転車も持っていこう。

ここで「自転車」のプランを足したとたんに、積んだ分のチェックが全部消えたら、困ります。

決まりを変えました。

  • プランを足すと、その中身のうち、まだないものだけが足される
  • プランを外すと、そのプランにしかないものだけが外れる
  • ほかのチェックは、そのまま残る

「ソロからファミリーに入れ替える」は、「ソロを外して、ファミリーを足す」になります。両方に入っているテントやバーナーのチェックは、残ります。

料理(#43)を作ったときに、同じ考え方を入れていました。料理を選び直すと、食材だけが足し引きされます。プランも、それに合わせた形です。

やり直したいときのボタン

チェックが消えなくなると、逆に、「最初からやり直したい」ときの手段がなくなります。

最初は、「キャンプを作り直せばよい」と考えました。Claude Code に聞くと、それで失うものを並べてくれました。キャンプ場の情報、選んだ料理、あとから足した項目。やり直したいのはチェックだけなのに、関係のないものまで入れ直すことになります。

持ち物チェックの下に、「最初の状態に戻す」のボタンを置きました。

確認のダイアログ。見出し「持ち物チェックを、最初の状態に戻しますか?」、本文「チェックと「今回不要」を、すべて外します。持ち物チェックのギアや消耗品は、消えません。」、ボタンは「やめる」と「最初の状態に戻す」

押すと、確認が出る

この確認の文は、2回直しました。

最初の文は、「チェックと「今回不要」を、すべて外しますか?」でした。これを見たわたしは、「今回不要も一緒に戻すように、変えてほしい」と頼みました。もう、そう動いていたのに、です。書いてあるのに、伝わっていませんでした。

次の案には、「並んでいるものは、そのまま残ります」とありました。「並んでいるものとは、何のこと?」と聞き返しました。

作った側は、画面の中身を知っています。「並んでいるもの」と書けば、持ち物チェックの項目のことだと分かります。使う側は、分かりません。「ギアや消耗品は、消えません」と、ものの名前で書き直しました。

確認の文は、「何が起きるか」だけでなく、「何が起きないか」も書きます。「最初の状態」と聞くと、あとから足したものまで消える、と読めるからです。


列を1つなくすだけ、のはずだった

ここからが、今回いちばん書いておきたい話です。

今までは、キャンプのテーブルに「プランの id」の列が1つありました。プランをいくつも選べるようにするので、キャンプとプランの組を持つ、新しいテーブルを作ります。

古い列は、どうするか。残しておくと、プランの持ち場所が2つになって、「どちらが正しいのか」で迷います。Issue には、こう書きました。

camps.plan_id の列はなくす(テーブルの作り直しになる。手元の D1 で、データが消えずに移ることを確かめてから出す)

SQLite(D1 の中身)では、ほかのテーブルを指している列は、そのままでは消せません。新しいテーブルを作って、中身を写して、古いテーブルを消して、名前を付け替えます。「テーブルの作り直し」です。マイグレーションの道具が、その SQL を自動で作ってくれます。

ハコヅミでは、前にも作り直しのマイグレーションを出したことがあります。そのときに決めた注意(外部キーの確かめを、あと回しにする書き方に変える)も、ちゃんと守りました。

そのうえで、Claude Code が、手元のデータベースで試しました。テストで溜まったデータが入った状態です。

前 後
キャンプ 71 71
持ち物チェックの項目(ギア) 58 0
持ち物チェックの項目(消耗品) 55 0
キャンプで作る料理 36 0
移したばかりのプラン — 0

キャンプは残っています。その中身が、全部消えました。

なぜ消えたか

持ち物チェックの項目には、「キャンプを消したら、その項目も一緒に消す」という決まりを付けてあります。持ち主のいない項目が残らないようにするための、ふつうの決まりです。

テーブルの作り直しでは、途中で古いテーブルを消します。データベースは、このとき、「中の行を全部消してから、テーブルを消す」という動きをします。

つまり、データベースから見ると、「全部のキャンプが消された」ことになります。決まりのとおりに、全部のキャンプの項目が、一緒に消されます。そのあとで新しいテーブルに名前を付け替えても、消えた項目は戻りません。

前に決めた注意は、「消したあとに、つじつまが合っているか」の確かめを、あと回しにするだけでした。「一緒に消す」動きそのものは、止まりません。

なぜ、今まで気づかなかったか

前に作り直しをしたときは、作り直したテーブルに、こういう「一緒に消える」子のテーブルが、ぶら下がっていませんでした。たまたま、踏まなかっただけです。

そして、マイグレーションは、空のデータベースでは、何の問題もなく通ります。消えるものが、ないからです。自動テストも、毎回まっさらなデータベースから始まるので、通ります。

データを入れた状態で試したから、分かりました。 そのまま本番に出していたら、全員の持ち物チェックが、消えていました。テスタがまだいないので、消えたのは、わたしのデータだけだったでしょう。それでも、これまでのキャンプの記録が、全部なくなるところでした。

列は、残すことにした

列をなくすのを、やめました。

  • マイグレーションは、新しいテーブルを足して、今のプランを写すだけ。作り直しは、しない
  • 古い列は、データベースに残る。コードからは、読まないし、書かない
  • コードには、「もう使わない列」と、残した理由を書いておく

「持ち場所を1つにする」という目的は、コードの上では守れています。使わない列が1つ残るのは、きれいではありません。でも、きれいにするために、データを危ない目にあわせる理由は、ありません。

決まりも、2つ足しました。

  • ほかのテーブルがぶら下がっているテーブルは、作り直さない
  • マイグレーションの確かめは、空ではなく、データを入れたデータベースで行う

Issue に「確かめてから出す」と一言書いてあったことが、効きました。書いていなければ、「道具が作った SQL だから」と、そのまま出していたかもしれません。


プランの欄の形を、2回変える

プランを選ぶ欄は、形が2回変わりました。

1回目:チェックを並べる

最初は、プランを全部並べて、チェックで選ぶ形でした。作ってもらった画面を見て、すぐに気づきました。プランが増えると、画面が長くなります。フリーでも、プランは 20 件まで作れます。

2回目:リストボックス

そこで、「リストボックスで、複数選べるようにしたい」と頼みました。5 行ぶんの枠の中で、スクロールして選ぶ形です。

Claude Code は、言われたとおりに作ったうえで、気になる点を2つ挙げてきました。

  • パソコンでは、2つ目を選ぶときに、Ctrl(Mac は ⌘)を押しながら押す必要がある。押さずにクリックすると、ほかの選択が全部外れる
  • iPhone では、見た目がまったく変わる(1行の欄になり、押すと選ぶ画面が出る)

1つ目は、こわい動きです。気づかずに保存すると、外れたプランにしかない荷物が、持ち物チェックから消えます。

3回目:選んだものだけ出して、残りは畳む

Claude Code が出した別の案は、料理の食材を選ぶ欄(#55)と、同じ形でした。

キャンプの編集の画面のプランの欄。選んである「ソロ」「自転車」だけがチェック付きで出ていて、その下に、閉じた「ほかのプランから選ぶ」

選んであるプランだけが出る

「ほかのプランから選ぶ」を開いたところ。カヌー・デュオ・ファミリー・雪中・釣りが並び、釣りにチェックが付いている。見出しに「(1 件 選択中)」

開くと、残りのプランが出る

選んであるプランだけが出て、残りは畳まれています。プランが 20 件あっても、欄の高さは、選んだ数で決まります。

自分で「リストボックス」と言い出したので、最初は、それが答えだと思っていました。欲しかったのは、「プランが増えても、画面が長くならないこと」です。リストボックスは、そのための手段の1つでした。

頼むときは、やり方ではなく、困っていることを伝えるほうが、よい案が返ってきます。今回は、やり方で頼んだのに、困っていることのほうを汲んで、別の案を出してくれました。

一覧のほうは、すんなり決まりました。リスト表示は、プランの札を選んだ数だけ並べます。カード表示は幅が狭いので、「ソロ ほか1」と省略します。

キャンプの一覧のリスト表示。1件目のキャンプに「ソロ」「自転車」の札が2つ並んでいる。2件目は「デュオ」の札が1つ

リスト表示。札が、選んだ数だけ並ぶ

キャンプの一覧のカード表示。1件目のカードの札は「ソロ ほか1」、2件目は「デュオ」

カード表示。2つ以上は「ほか1」


チェックを付けると、欄が閉じる

畳む形にしたあとで、E2E テストが、不具合を1つ見つけました。

「ほかのプランから選ぶ」を開いて、プランにチェックを付ける。すると、その瞬間に、開いていた欄が閉じます。2つ目を選ぼうとすると、もう一度開かないといけません。

原因は、「初めから開いておくか」の書き方でした。まだ1つも選んでいないとき(登録の画面)は、欄を開いておきたい。そこで、「開くかどうか」を、値で渡していました。

Svelte は、画面の中の何かが変わると、関係するところを描き直します。チェックを付けると、見出しの「1 件 選択中」が変わります。そのついでに、「開くかどうか」も、初めの値(閉じる)に戻されていました。人が手で開いたことは、Svelte は知りません。

「開いておく」ほうと「閉じておく」ほうを、別々に、決まった形で書くように直しました。

この不具合は、画面イメージの絵を撮ったときに、もう出ていました。「開いて、チェックを付けて、撮る」の順で撮った絵が、なぜか閉じた状態で写っていたのです。そのときは、撮る順番をまちがえたと思って、流しました。

「あれ?」と思ったことを、「自分のまちがいだろう」で片づけると、不具合を見逃します。テストに「チェックを付けても、欄は開いたまま」と書いていたので、拾えました。


3回続けて、書き忘れる

前回、リリースの PR に書く「マージの直前の時刻」を、2回続けて書き忘れた話を書きました。そのときに決めたのは、「Claude Code のほうから、マージの前に一言たしかめる」でした。

今回のリリースにも、マイグレーションが入っています。Claude Code は、PR の本文の一番上に、警告の印つきで欄を置きました。チャットでも、「書いたら、一言ください」と念を押しました。

わたしは、PR を開いて、そのままマージしました。3回目です。

言い訳をすると、PR を開いたときには、もう「マージする」つもりになっています。本文は、読んでいません。どれだけ目立つところに書いてあっても、読まないものは、目に入りません。

Claude Code の提案は、こうでした。

人が書く形をやめる案を出す。リリースの PR を作るときに、私がその時点の時刻を本文に書いておく。

PR を作った時刻でも、マイグレーションが当たる前であることは、変わりません。戻す先として、使えます。マージのあとで、GitHub に残っているマージの時刻に、書き換えてもらいます。

人が忘れても、困らない形です。

やり方 結果
1回目 PR の本文に、書く欄を置く 書き忘れた
2回目 同じ 書き忘れた。「次は、Claude Code がたしかめる」と決めた
3回目 欄を一番上に置き、チャットでも念を押す 書き忘れた
これから Claude Code が書く 人は、何もしなくてよい

前回は、「同じ失敗を2回したら、手順を変える」と書きました。変えたつもりの手順(一言たしかめる)は、まだ、人が何かをすることに頼っていました。

手順を変えるなら、「人が、もっと気をつける」ほうへではなく、「人が、何もしなくて済む」ほうへ変えます。


おわりに

  • 組み合わせで増えるものは、部品にして、足して使えるようにする
  • 足せるようにしたら、「変えたら最初から」の決まりを見直す。代わりに、やり直す手段を別に用意する
  • 確認の文は、作った側の言葉ではなく、ものの名前で書く。「何が起きないか」も書く
  • テーブルの作り直しは、ぶら下がっているテーブルの中身を、全部消すことがある。ほかのテーブルがぶら下がっているテーブルは、作り直さない
  • マイグレーションは、データを入れたデータベースで試す。空のデータベースでは、消えるものがないので、何も起きない
  • きれいにするために、データを危ない目にあわせない。使わない列は、残しておけばよい
  • 頼むときは、やり方ではなく、困っていることを伝える
  • 「あれ?」と思ったことを、自分のまちがいで片づけない
  • 手順を変えるなら、人が何もしなくて済むほうへ変える

次回は、今シーズン最後のキャンプで、実際に使ってみた話を書く予定です。

← トップページに戻る