Ruby Mini Projects — Build Real Applications Using Ruby Concepts
In this tutorial, you will learn about Ruby Mini Projects. We cover key concepts, practical examples, and best practices to help you master this topic.
Ruby mini projects applying classes, metaprogramming, threads, and gems to build a CLI task manager, web scraper, file watcher, and API client.
What You'll Learn
- Building a CLI task manager
- Creating a web scraper
- Building a file watcher
- Creating an API client gem
Why It Matters
Projects solidify your learning. These projects mirror real-world tools used at companies like GitHub (Ruby-based), Shopify, and DodaTech.
Real-World Use
CLI tools for DevOps, web scrapers for data collection, file watchers for CI/CD, API clients for Microservices.
flowchart LR
A["Projects"] --> B["CLI Task Manager"]
A --> C["Web Scraper"]
A --> D["File Watcher"]
A --> E["API Client Gem"]
A:::current --> B
style A fill:#2563eb,stroke:#2563eb,color:#fff
style B fill:#dbeafe,stroke:#2563eb,color:#1e40af
style C fill:#dbeafe,stroke:#2563eb,color:#1e40af
style D fill:#dbeafe,stroke:#2563eb,color:#1e40af
style E fill:#f1f5f9,stroke:#94a3b8,color:#64748b
CLI Task Manager
#!/usr/bin/env ruby
require "json"
require "fileutils"
class TaskManager
FILE = "tasks.json"
def initialize
@tasks = load_tasks
end
def run(args)
case args.first
when "add"
add(args.drop(1).join(" "))
when "list"
list
when "done"
done(args[1].to_i)
when "delete"
delete(args[1].to_i)
else
puts "Commands: add, list, done, delete"
end
end
private
def load_tasks
File.exist?(FILE) ? JSON.parse(File.read(FILE)) : []
rescue
[]
end
def save
File.write(FILE, JSON.pretty_generate(@tasks))
end
def add(description)
@tasks << { id: @tasks.size + 1, description: description, done: false }
save
puts "Added: #{description}"
end
def list
@tasks.each do |task|
status = task["done"] ? "[x]" : "[ ]"
puts "#{task['id']}. #{status} #{task['description']}"
end
end
def done(id)
task = @tasks.find { |t| t["id"] == id }
task["done"] = true if task
save
end
def delete(id)
@tasks.reject! { |t| t["id"] == id }
save
end
end
TaskManager.new.run(ARGV)
Web Scraper
require "net/http"
require "nokogiri"
class Scraper
def initialize(url)
@url = url
end
def fetch
uri = URI(@url)
response = Net::HTTP.get(uri)
doc = Nokogiri::HTML(response)
doc.css("h1").each { |h| puts h.text }
end
end
Scraper.new("https://example.com").fetch
File Watcher
require "fileutils"
class FileWatcher
def initialize(directory)
@dir = directory
@files = {}
end
def watch
loop do
Dir.glob("#{@dir}/*").each do |file|
mtime = File.mtime(file)
if @files[file] && @files[file] != mtime
puts "Modified: #{file}"
elsif !@files[file]
puts "New: #{file}"
end
@files[file] = mtime
end
sleep 1
end
end
end
FileWatcher.new(".").watch
API Client Gem Structure
# lib/weather_client.rb
require "net/http"
require "json"
class WeatherClient
BASE_URL = "https://api.weather.gov"
def initialize
@headers = { "User-Agent" => "WeatherClient/1.0" }
end
def forecast(lat, lon)
points = get("/points/#{lat},#{lon}")
forecast_url = points.dig("properties", "forecast")
get(forecast_url)
end
private
def get(path)
uri = URI(path.start_with?("http") ? path : "#{BASE_URL}#{path}")
response = Net::HTTP.start(uri.host, uri.port, use_ssl: true) do |http|
http.get(uri, @headers)
end
JSON.parse(response.body)
end
end
client = WeatherClient.new
puts client.forecast(39.7456, -97.0892)
Common Mistakes
1. No Error Handling
Always wrap API calls and file operations in begin/rescue blocks.
2. Hardcoded Paths
Use File.expand_path or relative paths. Never hardcode absolute paths.
3. No Input Validation
Check ARGV length before accessing elements. Validate user input.
4. Forgetting to Save
File-based apps must save after every mutation. Write tests to verify.
5. Blocking the Main Thread
Use threads or Ractors for long-running operations in watchers.
Practice Questions
1. How does the task manager persist data? Via JSON file. Tasks are loaded on init and saved after every mutation.
2. How does the file watcher detect changes? Compares File.mtime timestamps. Polls every second.
3. How does the API client handle HTTPS? Uses Net::HTTP with use_ssl: true for HTTPS connections.
4. What gem does the scraper use? Nokogiri for HTML Parsing, Net::HTTP for fetching pages.
Challenge: Add a --due DATE option to the task manager tasks and list tasks due today.
Solution
def add(description, due_date = nil)
@tasks << {
id: @tasks.size + 1,
description: description,
done: false,
due: due_date
}
save
end
def due_today
today = Date.today
@tasks.select { |t| t["due"] == today.to_s }
end
FAQ
{{< faq question="How do I distribute my CLI tool?" >}}
Package it as a gem with an executable. Use spec.executables in your gemspec. Users install with gem install your_tool.
{{< /faq >}}
{{< faq question="How do I add color to CLI output?" >}}
Use the colorize gem or ANSI escape codes. puts "\e[31mRed text\e[0m" for red text.
{{< /faq >}}
{{< faq question="What's the best way to handle command-line arguments?" >}}
Use the OptionParser standard library or the thor gem for more complex CLIs.
{{< /faq >}}
{{< faq question="How do I make HTTP requests?" >}}
Use the standard net/http library or the httparty and faraday gems for more features.
{{< /faq >}}
{{< faq question="Should I use threads or Ractors for concurrent scraping?" >}} Threads for I/O-bound scraping (waiting for responses). Ractors for CPU-bound parsing. Use a thread pool to limit connections. {{< /faq >}}
Try It Yourself
Run the task manager:
ruby task_manager.rb add "Learn Ruby"
ruby task_manager.rb list
ruby task_manager.rb done 1
Expected output:
Added: Learn Ruby
1. [ ] Learn Ruby
1. [x] Learn Ruby
What's Next
Now that you've built projects, explore the Ruby ecosystem and community.
| Topic | Description | Link |
|---|---|---|
| Ruby Ecosystem | Community and tools | {{< ref "46-ecosystem" >}} |
| Ruby Deployment | Deploying Ruby apps | {{< ref "48-deployment" >}} |
| Go Mini Projects | Compare Go projects | Go |
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro