Video transcript & code
A number of UNIX network utilities make use of the .netrc file to retrieve login credentials for remote hosts. This file normally lives in a user's home directory and is locked down to only be readable by that user. Here's an example of a .netrc file. Each entry consists of a machine name, a login, and a password.
machine rubytapas.com login avdi password xyzzy machine example.org login marvin password plugh
For more information on the .netrc file format, look up the relevant manpage.
There is a Ruby gem that makes it easy to read and write .netrc files. To use it, we can tell it to read in a .netrc file. Then we can look up a machine by name. The return value from this lookup is a two-element array. The first element is the login, and the second element is the password.
require 'netrc' netrc = Netrc.read('.netrc') entry = netrc['rubytapas.com'] entry # => "avdi" entry # => "xyzzy"
You may remember from episode 81 that it's possible to implicitly splat out an array into separate variables. This gives us a convenient way to assign both the login and the password at once.
require 'netrc' netrc = Netrc.read('.netrc') login, password = netrc['rubytapas.com'] login # => "avdi" password # => "xyzzy"
Looking at this interface, we feel like it might be nice if a .netrc entry were represented as a more meaningful object than an array. For instance, how about a
class Netrc Entry = Struct.new(:login, :password) end
As you may recall from episode 20, Structs are very flexible objects. With a
Struct representing a .netrc entry, we can access the username and password through method calls, or using Hash-style notation. But not only that, we can even access them using array indices - exactly the way the original two-element array worked.
class Netrc Entry = Struct.new(:login, :password) end entry = Netrc::Entry.new('avdi', 'xyzzy') entry.login # => "avdi" entry.password # => "xyzzy" entry[:login] # => "avdi" entry[:password] # => "xyzzy" entry # => "avdi" entry # => "xyzzy"
In fact, it seems like our
Entry Struct would be a perfect replacement for the old array-style entries. Except for one little problem.
When we try to assign the attributes of the entry to
password local variables, using the implicit splatting semantics we relied on earlier, it doesn't work. Instead, the first variable receives the entire struct, and the second variable remains unset.
class Netrc Entry = Struct.new(:login, :password) end entry = Netrc::Entry.new('avdi', 'xyzzy') login, password = entry login # => #<struct Netrc::Entry login="avdi", password="xyzzy"> password # => nil
In episode 81 we said that implicit splatting only works on arrays and "array-like" objects. So what, exactly, qualifies an object as "array-like"? Well, as it turns out, Ruby makes this determination based on whether the object responds to the
Array objects respond to this method, but our struct does not.
class Netrc Entry = Struct.new(:login, :password) end entry = Netrc::Entry.new('avdi', 'xyzzy') ['avdi', 'xyzzy'].respond_to?(:to_ary) # => true entry.respond_to?(:to_ary) # => false
Fortunately, this is easy to change.
Entry objects already respond to
#to_a, which is standard
Struct behavior. All we need to do, then, is alias their
#to_a method to
Entry objects can be implicitly splatted out just like arrays.
class Netrc Entry = Struct.new(:login, :password) do alias_method :to_ary, :to_a end end entry = Netrc::Entry.new('avdi', 'xyzzy') login, password = entry login # => "avdi" password # => "xyzzy"
Now we have a class that really would make a good replacement for the old two-element-array return value. It preserves the semantics of the original, so no existing client code should be affected by it. And it adds new, meaningful accessor methods.
This seemed like a good enough idea to me that I went ahead and sent a pull request to the netrc gem project, and it was accepted and merged in. It might even be rolled into the latest gem release by the time you watch this.