One of the most lauded features of Ruby is its support for the technique known as duck typing. Despite the humorous name, duck typing enables Ruby developers to write succinct, clean, and readable code with very little effort—all the things that attracted us to Ruby in the first place.
“What would Duck Dodgers do?”
The basic premise of duck typing is simple. If an entity looks, walks, and quacks like a duck, for all intents and purposes it’s fair to assume that one is dealing with a member of the species anas platyrhynchos. In practical Ruby terms, this means that it’s possible to try calling any method on any object, regardless of its class.
To demonstrate, here’s a simple scenario: you’re working with a system that tracks shipping manifests for each container on a ship. Data for each manifest is easily obtained through a RESTful web service:
# Fields: # * package_count: number of packages in manifest (integer) # * mass: total mass of all packages, in kilograms (BigDecimal) class ShippingManifest attr_accessor :package_count, :mass end
ShippingManifest tracks the number of packages, as well as the combined mass for all the packages. Your task is to take a set of manifests (provided in an
Array) and give a summation of the total mass and the total number of packages in the set.
“You say the Loch Ness Monster is living in your jacuzzi?”
The problem looks simple enough, right? We could probably bash it out in nine or ten lines of code:
def summarise_manifests # Grab the latest set of Manifests (returns an Array) manifest_set = ShippingManifest.all # Initialize our counters total_package_count = 0 total_mass = BigDecimal("0") manifest_set.each do |m| total_package_count += m.package_count total_mass += m.mass end return [total_package_count, total_mass] end
Easy, but verbose. It’s possible to reduce this method to a single line of code:
def summarise_manifests ShippingManifest.all.sum end
How? Duck typing!
“Skeleton key, eh? Where did you pick that up?”
Our one-line solution takes advantage of one of Ruby’s most powerful features: the
Enumerable provides an API to easily manipulate collections of objects—most commonly
Specifically, we’re using a method defined in
sum, which is available in every Rails project. As the name suggests, it’s used to calculate the sum of a set of objects. It does this by using
inject, a function that uses a block to reduce a set of objects to a single value. In the case of
sum, it does this by using the addition operator.
This makes perfect sense when we’re talking about numbers:
1 + 1 = 2 is fundamental arithmetic. And it’s also easy to see how it can be used to concatenate strings together:
['foo', 'bar', 'baz'].sum # => 'foobarbaz'
But how does Ruby know how to add two instances of our
Easy. We tell it how.
“That’s only part of who I am. I’m actually quite complex.”
“Everything is an object” is one of Ruby’s core mantras. The implication of this is that (nearly) every operator is actually a method call on an instance of an object—which grants us the ability to define our own implementation for that operator. It means we can do this:
class ShippingManifest < ActiveResource::Base # Returns a new instance of ShippingManifest containing the # sum of both `mass` and `package_count` def +(other) self.dup.tap do |neu| neu.mass = @mass + other.mass neu.package_count = @package_count + other.package_count end end end
By adding a few simple lines of code, we’ve added functionality that has made the potential
ShippingManifest much more usable by the wider Ruby library.
“And let me remind you again, folks, that you’re listening to Truth or—Aaaugh!”
Implementing custom behavior for the
+ operator only scratches the surface of what’s possible through duck typing. There are other operators that have custom behavior implemented:
== can have custom behavior. Need to sort a set of records? Implement the comparison operator (
<=>) in your classes and you’ll be able to take full advantage of the sorting methods provided in
As always, some example code for this post is available on our GitHub account—feel free to fork it and experiment! For starters, try implementing behaviour for
<=>, then experiment with the methods in
If you have queries about any other aspect of Ruby, feel free to ask in the comments!