How To Remove Argument Order Dependency In Ruby

Argument order dependency

How many times have you disconsidered an external Ruby library only because it didn’t adhere to Object Oriented Programming principles?

I’ve been there too, it sucks. You want your code to be as stable as possible and easy to change. You don’t want to polute it by introducing fragile dependencies. But while that’s a respectable goal to strive for, in practice, you don’t always have that luxury.

Let’s look at some example code.

class MyClass
  def initialize
    @person = ThirdParty::Person.new("John", "Doe", 23)
  end
end

Can you tell what is wrong with that code?

Well, MyClass knows too much. It knows the name of the Person class, the fact that it’s initializer requires three arguments, and the order of those arguments.

That means that whenever Person changes, your class will have to change also. You don’t want that. That’s not how you build code that’s easy to extend.

There are two different dependencies here. The first one is on the name of the dependency (the Person class), and the second one is about the order of its arguments. The later makes the subject of this article.

So how do you fix it? Obviously, you cannot change Person’s initializer method since you don’t own that dependency (its not your code). So, how do you fix it?

Well… you can use a factory object.

What is a factory object?

A factory object is an object that exists for the sole purpose of creating other objects.

I bet you didn’t get anything useful out of that sentence. I know, so let’s see what it means.

We will use a factory object to wrap the Person class.

module Wrapper
  def self.person(**args)
    ThirdParty::Person.new(args[:first_name], args[:last_name], args[:age])
  end
end

class MyClass
  def initialize
    @person = ::Wrapper.person(first_name: "John", last_name: "Doe", age: 23)
  end
end

UPDATE: Thank you Geo for pointing out the double splat operator.

In the code above, the Wrapper module is a factory object. It’s sole purpose is to initialize a new Person object. But it does so in a special way, it doesn’t just mimic Person’s initializer. It creates a different way to initialize a new Person object.

By transforming the method signature into a hash, you’ve accomplished two things.

  1. You’ve provided a form of documentation so people know what those arguments mean only by looking at the signature of the initializer.
  2. You’ve made the position of each argument irrelevant. Bingo!

For this tiny example, the benefits might not be obvious, but it’s easy to imagine how a factory could help when the method being wrapped takes many more arguments.

I’m using a module here instead of a class because it communicates to your future self and other deveolopers that you don’t intend to instantiate it. You just need it to handle a message.

If you liked this article, please take a moment and say thanks by sharing it on your favorite social media channel.