Custom Splat
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.
man netrc
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[0] # => "avdi"
entry[1] # => "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 Struct
with login
and password
fields?
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[0] # => "avdi"
entry[1] # => "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 login
and 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 #to_ary
method. 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 #to_ary
. Now, 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.
Happy hacking!
Responses