Ruby praises itself as the language that “makes programmers happy”. It does its best to be expressive and encourage a declarative style of programming.
Guillaume B. inherited a module which was supposed to simply check that a hash contained four fields which represented a signer: first name, last name, mobile, and email. There are many easy ways to check this using various collection methods (something like required_fields.all {|x| input_hash.has_key? x}
is the first that comes to mind).
Why on Earth should we do it the easy way? Ruby has powerful features, like mix-ins, list expansions, and message passing. Guillaume’s predecessor wasn’t going to let any of those features sit unused. Comments below are my own.
module SignService
class Signer
ATTRIBUTE_KEYS = :firstname, :lastname, :mobile, :email
attr_accessor *ATTRIBUTE_KEYS
def initialize signer
@firstname = signer[:firstname]
@lastname = signer[:lastname]
@mobile = signer[:mobile]
@email = signer[:email]
ATTRIBUTE_KEYS.each do |needed_attributes|
raise "missing attribute: #{needed_attributes} to instanciate a SignService::Signer" if send(needed_attributes).empty?
end
end
def params
{
firstname: @firstname,
lastname: @lastname,
phoneNum: @mobile,
emailAddress: @email
}
end
end
end
There’s so much hideous beauty in this, we have to take a moment to appreciate it. First, this pair of lines:
ATTRIBUTE_KEYS = :firstname, :lastname, :mobile, :email
attr_accessor *ATTRIBUTE_KEYS
First, we make a list of symbols. Then we pass that list (using the “*” operator to turn it into a series of arguments) to the attr_accessor function, which generates getter/setter methods for each of those symbols.
Most people would have just written: attr_accessor :firstname, :lastname, :mobile, :email
, but the splat operator shows a much deeper understanding of the Ruby language.
The function params
at the end of the class simply turns the instance level variables back into a hash, which is nice, but remember our goal is just to verify that the input hash contains the keys we require.
And that’s where the initialize
method comes in. The parameter signer
is our input hash, and the first few lines do their job neatly- they stuff the hash values into instance variables.
It’s the last line of the initialize method that holds our real WTF. This method commits the worst sin it possibly could: it gets “clever”.
ATTRIBUTE_KEYS.each do |needed_attributes|
raise "missing attribute: #{needed_attributes} to instanciate a SignService::Signer" if send(needed_attributes).empty?
We start with a simple for-each loop, and then we do a “raise … if”. Raise an error if the following condition is met. And that condition?: send(needed_attributes).empty?
. The “send” function exposes Ruby’s “message passing” approach to calling functions. This statement calls one of the functions generated in the attr_accessor
line and then checks if the result is empty?
.
From this code, we can conclude that the original developer knows quite a bit about the Ruby language. We can also conclude that they don’t know nearly enough about programming.