Skip to the content.
Systems Series Part 4

GitHub Copilot: Zero to Hero — Build Your First Ruby Project with AI Pair Programming

Learn Copilot by building a complete Ruby CLI Todo app from scratch with AI pair programming — no fluff, just code.

Suma Manjunath
Author: Suma Manjunath
Published on: August 26, 2025

Survival

Audience Software engineers, backend developers, tech leads
Reading Time: ~50 minutes
Prerequisites: Ruby installed (ruby -v), basic CLI knowledge, VS Code with GitHub Copilot & Copilot Chat extensions
Why now: AI coding assistants like GitHub Copilot are rapidly becoming standard in professional workflows. Teams adopting it effectively see up to 30% faster feature delivery.

TL;DR:

⚠️ Disclaimer: All scenarios, accounts, names, and data used in examples are not real. They are realistic scenarios provided only for educational and illustrative purposes.


Problem Definition

The challenge: Developers want to learn GitHub Copilot quickly but most resources are abstract or only cover autocomplete tricks.
Who faces this: Engineers new to Copilot or teams evaluating rollout for real projects.
Cost of inaction: Without structure, developers waste time experimenting, produce fragile code, and miss Copilot’s full potential (tests, refactoring, docs).
Why standard advice fails: Most tutorials show isolated prompts, not how to go from a blank folder to a working, tested, persistent app.


Solution Implementation

Approach: Learn Copilot by building a complete Ruby CLI Todo app, guided step-by-step through generation, refactoring, testing, persistence, and documentation.

Step 1: Setup (~5 minutes)

mkdir todo_ruby && cd todo_ruby
code .

💡 Tip: Copilot works with Ruby out of the box — no config needed.


Step 2: Generate the Core Class (~5 minutes)

Create todo.rb:

# A simple command-line Todo app in Ruby

class TodoApp
  def initialize
    @tasks = []
  end

  def add_task(task)
    @tasks << { text: task, done: false }
  end

  def list_tasks
    @tasks.each_with_index do |task, i|
      status = task[:done] ? "[x]" : "[ ]"
      puts "#{i+1}. #{status} #{task[:text]}"
    end
  end

  def mark_done(index)
    @tasks[index][:done] = true if @tasks[index]
  end
end

✔️ Skeleton complete.


Step 3: Build a CLI Runner (~10 minutes)

class TodoApp
  # ... same as above ...
end

# CLI runner
app = TodoApp.new

loop do
  puts "Commands: add <task>, list, done <index>, quit"
  input = gets.strip.split(" ", 2)
  command, arg = input[0], input[1]

  case command
  when "add"
    app.add_task(arg)
  when "list"
    app.list_tasks
  when "done"
    if arg && arg.to_i > 0
      app.mark_done(arg.to_i - 1)
    else
      puts "Invalid index"
    end
  when "quit"
    break
  else
    puts "Unknown command"
  end
end

✔️ Working CLI app with error handling.


Step 4: Add Tests (~10 minutes)

test_todo.rb:

require "minitest/autorun"
require_relative "todo"

class TodoAppTest < Minitest::Test
  def setup
    @app = TodoApp.new
  end

  def test_add_task
    @app.add_task("Buy milk")
    assert_equal 1, @app.instance_variable_get(:@tasks).length
  end

  def test_list_tasks
    @app.add_task("Buy eggs")
    assert_output("1. [ ] Buy eggs
") { @app.list_tasks }
  end

  def test_mark_done
    @app.add_task("Do laundry")
    @app.mark_done(0)
    assert @app.instance_variable_get(:@tasks)[0][:done]
  end

  def test_mark_done_with_invalid_index
    @app.add_task("Cook dinner")
    assert_silent { @app.mark_done(99) }
  end
end

✔️ Full happy + sad path test coverage.


Step 5: Add Persistence (~10 minutes)

Update todo.rb:

require "json"

class TodoApp
  def initialize(file = "tasks.json")
    @file = file
    @tasks = File.exist?(@file) ? JSON.parse(File.read(@file), symbolize_names: true) : []
  end

  def save
    File.write(@file, JSON.pretty_generate(@tasks))
  end

  def add_task(task)
    @tasks << { text: task, done: false }
    save
  end

  def mark_done(index)
    @tasks[index][:done] = true if @tasks[index]
    save
  end
end

✔️ App persists across runs.


Step 6: Refactor with Copilot (~5 minutes)

Prompt Copilot: “Refactor TodoApp to use a Task class instead of hash, keeping API the same.”

Copilot suggests:

Task = Struct.new(:text, :done, keyword_init: true)

✔️ Modernized, idiomatic Ruby.


Step 7: Generate Documentation (~2 minutes)

Copilot can generate a README.md with commands and usage examples automatically.


Step 8: Run the App (~5 minutes)

ruby todo.rb

Example session:

> add Learn Copilot
> add Write blog post
> list
1. [ ] Learn Copilot
2. [ ] Write blog post
> done 1
> list
1. [x] Learn Copilot
2. [ ] Write blog post
> quit

✔️ End-to-end project complete.


Validation & Monitoring

Warning: Copilot defaults to happy paths — always test invalid cases.


Takeaways


Next Steps

Extend your app:

Level up with Copilot:


Acronyms


References

  1. GitHub Copilot Docs - GitHub Copilot Documentation, 2024
  2. Ruby Style Guide - Ruby Style Guide, 2024
  3. Minitest Framework - Minitest GitHub, 2024

Comments & Discussion

Share your thoughts, ask questions, or start a discussion about this article.