11日目のゴールとテーマ
11日目のテーマは「タスク管理アプリに“記憶”と“締め切り”を与える」です。
10日目で、Task クラスとメニュー付きのタスク管理アプリの骨格ができました。
今日はそこから一歩進めて、
タスクをファイルに保存・読み込みできるようにする
Task に「締め切り日(deadline)」を追加する
締め切りつきで表示を少しリッチにする
という流れで、「自分で使えるタスクアプリ」に近づけていきます。
10日目までのタスク管理アプリを整理する
いま持っている部品を言葉で確認する
ここまでで、だいたい次のような構成になっているはずです。
Task クラス
タイトル(title)、メモ(memo)、完了フラグ(done)を持つ。
summary で一覧用の1行表示、detail で詳細表示を返す。
mark_done / mark_undone で完了状態を切り替えられる。
tasks 配列
Task オブジェクトを複数まとめて持つ「タスク一覧」。
メニューとメインループ
「一覧表示」「追加」「完了にする」「終了」が選べる。
今日はここに「保存」「読み込み」「締め切り」を足していきます。
Taskに「締め切り日」を追加する
まずは“どう扱いたいか”を決める
締め切り日(deadline)をどう扱うか、先に決めておきます。
文字列として扱う(例: “2025-03-20” や “明日” でもOK)
必須ではなく「空でもよい」(締め切りなしタスクも許す)
表示のときに「締め切り: ○○」と出したい
最初から日付型や厳密なバリデーションを入れると難しくなるので、
11日目では「締め切りは文字列で、あくまでメモ」として扱います。
Taskクラスを書き換える
class Task
attr_accessor :title, :memo, :done, :deadline
def initialize(title, memo, deadline = "", done = false)
@title = title
@memo = memo
@deadline = deadline
@done = done
end
def mark_done
@done = true
end
def mark_undone
@done = false
end
def done_mark
@done ? "[x]" : "[ ]"
end
def summary
if @deadline.nil? || @deadline.strip == ""
"#{done_mark} #{@title}"
else
"#{done_mark} #{@title}(締め切り: #{@deadline})"
end
end
def detail
text = ""
text += "#{done_mark} タイトル: #{@title}\n"
text += "メモ: #{@memo}\n"
if @deadline && @deadline.strip != ""
text += "締め切り: #{@deadline}\n"
else
text += "締め切り: (なし)\n"
end
text
end
end
Rubyここで深掘りしたいポイントは三つです。
一つ目は、initialize の引数の順番です。
title, memo は必須、deadline は任意、done も任意(デフォルト false)にしています。
Task.new(“買い物”, “牛乳を買う”) のように呼べば、締め切りなし・未完了のタスクになります。
二つ目は、summary の条件分岐です。
締め切りが空文字やスペースだけなら、締め切り表示を省略しています。
締め切りがあるときだけ「(締め切り: ○○)」を付けることで、一覧が見やすくなります。
三つ目は、detail で「締め切りなし」の場合も明示していることです。
「締め切り: (なし)」と出すことで、「設定されていないだけ」と分かります。
入力から締め切りを受け取る
build_task_from_inputを拡張する
締め切りを入力できるように、タスク入力メソッドを少し変えます。
def read_non_empty_line(message)
loop do
puts message
input = gets
return "" if input.nil?
text = input.chomp
if text.strip == ""
puts "空では登録できません。何か入力してください。"
else
return text
end
end
end
def build_task_from_input
title = read_non_empty_line("タスクのタイトルを入力してください:")
memo = read_non_empty_line("タスクのメモ(詳細)を入力してください:")
puts "締め切りを入力してください(未入力なら締め切りなし):"
deadline_input = gets
deadline = deadline_input ? deadline_input.chomp : ""
Task.new(title, memo, deadline)
end
Rubyここでの重要ポイントは、「締め切りは空でもOK」にしていることです。
タイトルとメモは read_non_empty_line で空を禁止している
締め切りは gets でそのまま受け取り、空なら空文字のまま Task に渡す
このくらいの“ゆるさ”から始めると、実装のハードルが下がります。
タスクをファイルに保存する方針を決める
どんな形式で保存するか
名簿アプリと同じように、「1タスク1行」のテキスト形式にします。
1行の中に「タイトル,メモ,締め切り,完了フラグ」をカンマ区切りで入れる
完了フラグは “0”(未完了)か “1”(完了)で表現する
例えば、こんな感じです。
Rubyの勉強,11日目をやる,2025-03-20,0
部屋の片付け,机の上をきれいにする,,1
二行目の締め切りが空なのは、「締め切りなし」を表しています。
Taskを「保存用の1行」に変換する
Taskに保存用メソッドを追加する
class Task
# さきほどの定義に続けて…
def to_csv_line
done_flag = @done ? "1" : "0"
"#{@title},#{@memo},#{@deadline},#{done_flag}"
end
end
Rubyここでの重要ポイントは、done をそのまま true/false で保存しないことです。
ファイルは文字列しか扱えないので、
“1” と “0” に変換して保存しています。
読み込むときに、”1″ なら true、”0″ なら false に戻します。
ファイルからTaskを復元する
1行からTaskを作るメソッド
def task_from_csv_line(line)
text = line.chomp
parts = text.split(",")
title = parts[0] || ""
memo = parts[1] || ""
deadline = parts[2] || ""
done_str = parts[3] || "0"
done = (done_str == "1")
Task.new(title, memo, deadline, done)
end
Rubyここで深掘りしたいポイントは二つです。
一つ目は、配列 parts の要素が足りない場合の保険です。|| "" や || "0" を入れておくことで、
多少フォーマットが崩れていても最低限動くようにしています。
二つ目は、done の復元です。
“1” なら true、そうでなければ false としています。
このように、「保存用の表現」と「アプリ内の表現」を変換するのがポイントです。
タスク一覧をファイルに保存・読み込みする
保存メソッドを書く
TASK_DATA_FILE = "tasks_data.txt"
def save_tasks(tasks)
File.open(TASK_DATA_FILE, "w") do |file|
tasks.each do |task|
file.puts task.to_csv_line
end
end
puts "タスクをファイルに保存しました。(#{TASK_DATA_FILE})"
end
Ruby重要なのは、Task#to_csv_line に責任を任せていることです。
save_tasks は「全タスクを1行ずつ書く」ことだけを考えればよくなります。
読み込みメソッドを書く
def load_tasks
tasks = []
unless File.exist?(TASK_DATA_FILE)
puts "タスクの保存ファイルがまだありません。(初回起動かもしれません)"
return tasks
end
begin
File.open(TASK_DATA_FILE, "r") do |file|
file.each_line do |line|
next if line.strip == ""
task = task_from_csv_line(line)
tasks << task
end
end
puts "タスクをファイルから読み込みました。(#{tasks.length}件)"
rescue => e
puts "タスク読み込み中にエラーが発生しました。"
puts "エラー内容: #{e.class} #{e.message}"
end
tasks
end
Rubyここでの重要ポイントは、「エラーが起きてもアプリ全体を止めない」ことです。
ファイルが壊れていても、
「読み込みに失敗したので、空のタスク一覧から始めます」といった振る舞いにできます。
メインループに保存・読み込みを組み込む
起動時に読み込み、終了時に保存する
タスク管理アプリのメイン部分を、こう書き換えます。
tasks = load_tasks
loop do
show_menu
choice = read_menu_number
if choice == 0
puts "アプリを終了します。"
save_tasks(tasks)
break
elsif choice == 1
handle_show_tasks(tasks)
elsif choice == 2
handle_add_task(tasks)
elsif choice == 3
handle_mark_task_done(tasks)
else
puts "不正な番号です。もう一度入力してください。"
end
end
Rubyこれで、
アプリ起動時に前回のタスク一覧を復元する
アプリ終了時に最新のタスク一覧を保存する
という流れができあがります。
締め切りつきタスク一覧を眺めてみる
実行イメージを頭に描く
例えば、次のようなタスクを登録したとします。
タイトル: Rubyの勉強
メモ: 11日目をやる
締め切り: 2025-03-20
タイトル: 部屋の片付け
メモ: 机の上だけでもきれいにする
締め切り: (未入力)
一覧表示はこんな感じになります。
========================
タスク一覧:
1. [ ] Rubyの勉強(締め切り: 2025-03-20)
2. [ ] 部屋の片付け
detail を表示するような機能を足せば、こうなります。
[x] タイトル: Rubyの勉強
メモ: 11日目をやる
締め切り: 2025-03-20
ここで感じてほしいのは、
締め切りがあるタスクとないタスクが、自然に混ざって表示できている
完了状態も [ ] / [x] で一目で分かる
保存しても、次回起動時に同じ状態が再現される
という「小さいけれどちゃんとしたツール感」です。
11日目のまとめ
今日の大事なポイントを短く整理します。
Task に deadline を追加し、「締め切りつきタスク」を表現できるようにした。
締め切りはまず文字列として扱い、「あるか・ないか」だけを意識した。
Task#to_csv_line と task_from_csv_line で、
「アプリ内の表現」と「保存用の1行」を相互変換できるようにした。
save_tasks / load_tasks で、タスク一覧をファイルに保存・読み込みできるようにした。
起動時に load_tasks、終了時に save_tasks を呼ぶことで、
アプリに“記憶”が宿った。
ここまで来ると、
「自分の生活のために書いたタスクアプリ」が
本当に日々の中で使えるレベルに近づいてきます。
次のステップでは、
締め切りが近いタスクだけを表示する
完了済みを非表示にするモードを作る
優先度(高・中・低)を持たせる
といった方向に広げていくこともできます。
あなたが今、「自分ならどんなタスクアプリを使いたいか」を
少し具体的にイメージできているなら、
それはもう立派に“Rubyで自分の道具を作り始めている人”です。
