My Favourite Parts of the Ruby Standard Library that You Might Not Know About
I was at Bath Ruby this week!
I’ve never managed to motivate myself to take decent notes at a conference, so this time I tried something different — I livetweeted the whole thing.
I also made last-minute decision to put my name down to do a lightning talk, and was fortunate enough to get the last slot. The talk was “My Favourite Parts of the Ruby Standard Library that You Might Not Know About”, and it was really well received. People were especially impressed with the way I handled some computer issues while getting set up, where I couldn’t get my computer to respond for a good couple of minutes after standing up at the speakers’ podium.
I used my new presenting setup for the second time here, which makes me even more confident this is how I’ll do it going forward: rather than trying to arrange attractive slides using Keynote or similar, I just created a load of text buffers in my editor, and cycled through them in order. This let me create my slides in an environment I was much more comfortable with and familiar in, in a plain text format that I can post directly on the web and reüse in future, and without graphical features that get me distracted trying to make everything pixel-perfect.
Here were the examples I gave (not necessarily in order):
Delegate
Create wrapper objects that can delegate to the object they contain. This is especially useful for implementing the decorator pattern, which I’ve done here with a decorator that wraps and object and logs all messages sent to it by other objects.
require "delegate"
class Greeter
def greet(name)
"Hello name"
end
end
class LogMessages < SimpleDelegator
def method_missing(name, *args)
result = super
Kernel.puts "#{__getobj__.inspect}.#{name}(#{args.map(&:inspect).join(", ")}) -> #{result.inspect}"
result
end
end
greeter = LogMessages.new(Greeter.new)
greeter.greet("BathRuby")
# -> #<Greeter:0x00007fadc98e6448>.greet("BathRuby") -> "Hello name"
Forwardable
Ruby has a built-in method delegation macro very similar to the much more commonly-known implementation provided by Rails. We can use it like this:
require "forwardable"
class User
extend Forwardable
# delegate the `name` message to profile
def_delegator :profile, :name
def profile
Profile.new(name: "Alyssa")
end
end
class Profile
attr_reader :name
def initialize(name:)
@name = name
end
end
puts User.new.name
# -> Alyssa
OptionParser
Ruby has a built-in library for parsing command line options, generating help text, etc., as would otherwise often be handled by an external Gem such as Thor:
#!/usr/bin/env ruby
require "optparse"
options = {}
OptionParser.new do |parser|
parser.banner = "Usage: #{File.basename(__FILE__)} [options]"
parser.on "--name NAME", String, "name of the person to be greeted" do |name|
options[:name] = name
end
end.parse!
puts "Hello #{options.fetch(:name)}"
This creates a script that can be used like this:
$ ./greet.rb --help
Usage: greet.rb [options]
--name NAME name of the person to be greeted
$ ./greet.rb --name BathRuby
Hello BathRuby
Note that OptionParser automatically intercepts --help
, prints automatically
formatted help text, then exits the program.
REXML
This is probably the one that least people knew about coming into my talk. Ruby has a built-in XML library, so you don’t necessarily need to include a big library like Nokogiri with a large native extension:
require "rexml/document"
xml = <<XML
<?xml version="1.0" encoding="UTF-8"?>
<conferences>
<conference name="BathRuby" />
<conference name="Brighton Ruby" />
</conferences>
XML
document = REXML::Document.new(xml)
document.root.get_elements("//conference")
# => [<conference name='BathRuby'/>, <conference name='Brighton Ruby'/>]
The downside to REXML is that it’s implemented in pure Ruby, so Nokogiri or another external XML library may well be worth including if adding a dependency is worth the performance gain of using a C implementation.
Shellwords
Shellwords lets you escape strings so that they can be interpolated into shell
commands. In most cases, this is unnecessary, since you can (and
should) pass dynamic values into shell commands in Ruby
using an arguments array (system "say", name
) rather than a single command
string (system "say #{name}"
). However, shellwords still comes in handy when
you want to pass a whole shell command as a single argument (for example to be
executed in a subshell). For example:
require "shellwords"
print "Enter name: "
name = gets.chomp
command = "echo Hello #{name.shellescape}; read"
system "tmux", "split-window", "bash", "-c", command
Singleton
The singleton pattern is often discouraged nowadays, but if you do find yourself in a position to be implementing it, the Ruby standard library has your back:
require "singleton"
class Twitter
include Singleton
def tweet(text)
puts "Tweeting #{text}"
end
end
Twitter.new.tweet("hello")
# ~> private method `new' called for Twitter:Class
Twitter.instance.tweet("hello")
# -> Tweeting hello
Conclusion
I put this talk together over a conference lunch, and only had five minutes on stage, so there’s loads more cool stuff in the Ruby standard library I could have gone into but didn’t. I highly encourage you to explore it for yourself. Some more of my favourites are Fiddle and StringScanner.