Ruby
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Methods and Blocks
-
Section 4: Object-Oriented Ruby
-
Section 5: Metaprogramming
-
Section 6: Working with Data and Files
-
Section 7: Ruby Frameworks Overview
-
Section 8: Ecosystem and Testing
-
Section 9: Practical Projects
-
Section 10: Interview Practice
-
Section 11: Data Structures and Algorithms in Ruby
-
Section 12: More Practice Exercises
-
Section 13: Enumerable and Functional Style
-
Section 14: More OOP Practice
-
Section 15: More Testing
-
Section 16: Enumerable and Comparable Modules In Depth
-
Section 17: Ruby Standard Library: Core Utilities
-
Section 18: Ruby Standard Library: Data and Security
-
Section 19: Ruby Standard Library: CLI and Text
-
Section 20: Ruby Networking
-
Section 21: Ruby on Rails Deep Dive
-
Section 22: Ruby Metaprogramming Deep Dive
-
Section 23: Ruby Design Patterns
-
Section 24: Ruby Concurrency
-
Section 25: Ruby Testing Deep Dive
-
Section 26: Ruby Gems and Packaging
-
Section 27: Ruby Performance
-
Section 28: More Practice Exercises
-
Section 29: More Interview Practice
-
Section 30: Rails API Development
-
Section 31: Rails Authentication and Authorization
-
Section 32: Rails Testing Deep Dive
-
Section 33: Rails Performance
-
Section 34: Rails Deployment
-
Section 35: Sinatra and Lightweight Ruby Web Apps
-
Section 36: More Ruby Language Deep Dive
-
Section 37: Ruby 3.x Modern Features
-
Section 38: More Data Structures in Ruby
-
Section 39: More Practical Projects
-
Section 40: Ruby Ecosystem Tools
-
Section 41: More Practice and Review
-
Section 42: Final Practice and Mastery
-
Section 43: Ruby Interview Deep Dive
-
Section 44: Ruby Background Processing Deep Dive
-
Section 45: Ruby GraphQL
-
Section 46: Ruby Object Model Deep Dive
-
Section 47: Ruby Hanami Framework Overview
-
Section 48: Ruby gRPC and Protocol Buffers
-
Section 49: Ruby Data Processing
-
Section 50: Ruby Search Integration
-
Section 51: Ruby File Upload and Media
-
Section 52: Ruby Email and Notifications
-
Section 53: Ruby Admin Panels
-
Section 54: Ruby Feature Flags and Experimentation
-
Section 55: Ruby Monitoring and Observability
-
Section 56: Ruby Docker and Deployment Deep Dive
-
Section 57: Ruby Security Deep Dive
-
Section 58: More Advanced Metaprogramming
-
Section 59: More Final Projects
74: OptionParser for CLI Argument Parsing
When you first start writing scripts that take command-line arguments, you'll likely lean on ARGV. It's just an array of strings, right? It feels intuitive to just grab ARGV[0] for your filename and ARGV[1] for a setting. I've seen countless developers try to build "professional" CLI tools by manually shifting and popping elements off that array or writing a complex while loop to find specific flags.
"ARGV is enough for simple flags" vs. "OptionParser handles the edge cases for you"
Here is why manual ARGV parsing is a trap. Imagine you're building a tool to compress images. You want a flag for quality (-q) and a flag for the output directory (-o). You might write something like this:
# The "naive" way
quality = 80
output = "./dist"
if ARGV[0] == "-q"
quality = ARGV[1].to_i
end
# ... and so on
This breaks the second a user decides to run ruby compress.rb -o ./backup -q 50 instead of -q 50 -o ./backup. Suddenly, your ARGV[0] is -o, and your quality logic fails. You could write a loop to search the array, but then you have to handle cases where a user provides --quality instead of -q, or forgets the value entirely. It's a rabbit hole of if/else statements that distracts you from the actual logic of your program.
Ruby's OptionParser library solves this by letting you define the rules for your arguments first, and then letting the library figure out where those arguments are hiding in the ARGV array, regardless of their order.
"Parsing is a manual loop" vs. "Defining options as a declarative block"
Instead of hunting through an array, you describe what your CLI expects. Let's build a hypothetical tool that processes a CSV file, allowing the user to specify a delimiter and whether they want a "verbose" output.
require 'optparse'
options = {}
# OptionParser.new takes a block where we define our flags
OptionParser.new do |opts|
opts.banner = "Usage: csv_tool.rb [options] file.csv"
# A simple switch (boolean)
opts.on("-v", "--verbose", "Run verbosely") do |v|
options[:verbose] = v
end
# An option that requires an argument
# The "DELIM" string here is just a placeholder for the help menu
opts.on("-d", "--delimiter DELIM", "Specify the CSV delimiter") do |d|
options[:delimiter] = d
end
end.parse! # The bang version modifies ARGV in place
# Now we can see what's left in ARGV
filename = ARGV[0]
puts "Processing #{filename} with delimiter #{options[:delimiter] || ','} (Verbose: #{!!options[:verbose]})"
Notice the parse! method. This is the "magic" bit. It iterates through ARGV, finds the flags you defined, executes their associated blocks, and—crucially—removes those flags from ARGV. By the time parse! finishes, ARGV only contains the positional arguments (like the filename), making your life significantly easier.
"Hardcoding help messages" vs. "Automatic help generation"
One of the most annoying parts of building a CLI is writing the --help menu. You update a feature, but forget to update the help text, and suddenly your documentation is lying to your users. I've been there, and it's a great way to get a grumpy bug report.
With OptionParser, the help menu is a byproduct of your definitions. Because we provided a banner and descriptive strings in the opts.on calls, OptionParser automatically handles the -h and --help flags for you. If you run the script above with ruby csv_tool.rb --help, it will print a perfectly formatted manual without you writing a single puts statement for the documentation. It's clean, it's maintainable, and it's the standard way to do things in the Ruby ecosystem.
📋 Practical Task
Build a System Log Filter Tool
Your task is to create a script called log_filter.rb that allows a user to filter a log file based on a severity level and a maximum number of lines to return.
The script should implement the following requirements using OptionParser:
- A
--level(or-l) option that takes a string (e.g., "ERROR", "WARN", "INFO"). This should be stored in anoptionshash. - A
--limit(or-n) option that takes an integer. Ensure you convert the input to an integer using.to_iinside the block. - A
--silent(or-s) switch that, when present, sets a boolean in youroptionshash. - The script must accept one positional argument: the path to the log file.
Expected behavior:
If the user runs ruby log_filter.rb -l ERROR -n 10 system.log, your script should print:
Filtering system.log for ERROR level, limiting to 10 results. Silent mode: false
Ensure that if no filename is provided as a positional argument, the script prints an error message and exits.
There are no comments for now.