Let's think this through for a second
In this final stage, we'll fix the problem where the app from Part 2 loses its data every time it runs by using File I/O to save and load the task list from disk. To keep the display format consistent, we'll pull it out into a Formatter module and write it as a module method (self.line) — a practical use of the modules concept. When the app starts, we'll also show a welcome message and command list to make the user experience nicer. By the end of this stage, you'll own a small command-line project that pulls together Ruby's class, module, file handling, block, and iterator concepts.
Let's build it for real
Write the Formatter module with a def self.line(task, index) method that returns a string combining a status icon (✔ or a space), the index, and the title — update list_tasks to call this module method. Add a save(filename) method to the TaskTracker class that uses File.open(filename, "w") do |f| to write each task to the file in "title|done" format. The load(filename) method should check whether the file exists with File.exist?, and if it does, use File.readlines to split each line with split("|") and rebuild new Task objects. When the app starts, call tracker.load first, and when the exit command comes in, call tracker.save to persist the data. Finally, show a help message listing the available commands when the app starts up.
Code Example
module Formatter
def self.line(task, index)
status = task.done ? "✔" : " "
"#{index}. [#{status}] #{task.title}"
end
end
class TaskTracker
def save(filename = "tasks.txt")
File.open(filename, "w") do |f|
@tasks.each { |t| f.puts "#{t.title}|#{t.done}" }
end
end
def load(filename = "tasks.txt")
return unless File.exist?(filename)
@tasks = File.readlines(filename).map do |line|
title, done = line.strip.split("|")
task = Task.new(title)
task.done = (done == "true")
task
end
end
def list_tasks
@tasks.each_with_index { |task, i| puts Formatter.line(task, i + 1) }
end
end
tracker = TaskTracker.new
tracker.load
puts "Task Tracker - commands: list / add <title> / done <no> / exit"
loop do
print "> "
input = gets.chomp
break if input == "exit"
# Part 2 ရဲ့ case/when logic ကို ဒီနေရာမှာ ဆက်သုံးပါ
end
tracker.saveAfter closing and reopening the app, it should load the data from tasks.txt and show your previous task list exactly as it was.5-Minute Challenge
Switch the save format from "title|done" to CSV-style "title,done,priority", including the priority attribute in save/load — try it in 5 minutes.
One Quick Warning
Ruby auto-closes the file for you once a File.open block finishes — but be careful not to call save/load inside a loop that runs frequently; only call them when you actually need to.