Thuta Learning
ProjectsProgrammingbeginner

Task Tracker Project - Part 3

Relax. We'll talk through this in plain words — no textbook voice.

What you'll walk away with

  • Apply Task Tracker Project - Part 3 in a hands-on project
  • Write and run your own code
  • Build out an entire project step by step

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

ruby
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.save
You should see
After 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.

Easy traps

  • Forgetting to check File.exist? in the load method, which causes an error the very first time the app runs before the file exists
  • Not noticing that done, once split from split("|"), is a "true"/"false" string rather than an actual boolean, and using it directly in logic that expects a real boolean

Try It Yourself

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.

You'll know it worked when: After closing and reopening the app, it should load the data from tasks.txt and show your previous task list exactly as it was.

Task Tracker Project - Part 3 | Thuta Learning