Contributing to Ruby
This is a short tale about how I almost became a Ruby language contributor back in 2019. What's with the almost part? Well, technically, I did contribute to the Ruby Core, but the part I wanted to contribute to initially was extracted from the code and became a stand-alone gem, though still distributed with MRI Ruby.
Ruby is by far my favorite do-it-all programming language, with over a decade of professional use on the clock. And yes, Ruby + Rails is a killer combo even by today's standards. But what really bummed me out back in 2019 was the fact that while I've used hundreds of open-source gems, contributed to many and created a bunch of my own, I had literally zero contributions to Ruby itself. And I mean, it wasn't perfect, but the general expectation was that others were to fix any issues.
The problem
There are several classes in the Ruby codebase (stdlib) responsible for networking
transport functionality, like Net::HTTP
, Net::FTP
and Net::SMTP
. While they
cover entirely different protocols, all of them have a rudimentary debugging mechanism
built in (aka debug logger). Well, except for Net::FTP
. And, coincidentally, I had
to troubleshoot a bunch of protocol-specific issues in FTP services for one of my clients.
Let's have a look at debugger setup in Net::HTTP
class:
require "net/http"
http = Net::HTTP.new(hostname)
http.set_debug_output $stderr
http.start { .... }
Similar happens in Net::SMTP
:
require "net/smtp"
smtp = Net::SMTP.new(addr, port)
smtp.set_debug_output $stderr
smtp.start { .... }
With set_debug_output
call, we are setting an internal @debug_output
variable
in each class. Depending on the protocol implementation, that roughly translates
into:
@debug_output << msg + "\n" if @debug_output
When debugging is enabled, we're able to troubleshoot any issues without having to
rely on monkey-patching of the transport classes and log all necessary debug messages
to STDOUT/STDERR or a log file. Unfortunately, this does not apply to Net::FTP
class.
That's what I decided to fix.
A temporary solution I had to rely on was a monkey-patch in the project's codebase:
require "net/ftp"
class FTPWithLog < Net::FTP
attr_accessor :debug_io
def print(*args)
(debug_io || STDOUT).write(args.join)
end
end
Mostly because Net::FTP
class had a debug_mode
setting, but no way of specifying
where the output should go (and thus no way of capturing it). FTP transport code
is sprinkled with debug code like this:
if @debug_mode
print "connect: ", host, ", ", port, "\n"
end
The solution
A straightforward fix came in a form of a PR to the Ruby Core. In fact, the said fix has been sitting in my branch since early 2019, but I didn't bother to formally submit it until later that year.
We're essentially adding the same debugging statement into the Net::FTP
class for
consistency reasons. Usage gist:
require "net/ftp"
conn = Net::FTP.new(...)
conn.debug_mode = true
conn.debug_output = STDERR
Due to various CI issues this PR was never merged, and has been marinating for like
2 years until one of the Ruby Core team member reached out and mentioned net/ftp
library
being extracted out of the core codebase. Perfect! I cut a new PR against
ruby/net-ftp
repo, and that work gets merged in a few weeks later.
Conclusion
My fixes in the Net::FTP
class are nothing to really be proud of, in fact,
those changes are pretty mundane. But the improvement is still an improvement,
and a step forward, regardless of how small it is. I'm pretty sure someone out there had to rely on the
same core class monkey-patching for troubleshooting. No more!
Changes are available in net-ftp/v0.2.0
release.
With that being said - don't be afraid to contribute back!