Flexible Ruby Value Object Initialisation
Tags: ruby, oo, valueobject, refine
Published: 2019-01-21 18:46:00

---
title: Flexible Ruby Value Object Initialisation
published: true
description: Implementing core object type conversions for happy programming with value objects in Ruby
tags: ruby,oo,value object,refine
series: Getting the Most Out of Ruby Value Objects
---

#Problem

Value objects in Ruby are lovely things, and invaluable in a complex application. 

But one issue that always seems to trip me up is correctly initialising from other objects, so while we might want to write:

```ruby
class ISBN
  def initialize(value)
    @isbn_string = value
  end

  def etc
  end
end
```

... it turns out in practice that sometimes you're creating a value object based on string input, and sometimes it might make sense to store the value in the database as a numeric but you can be sure that a controller will receive it as a string, and other times it already is a value object.

Enough of all that nonsense – you need to be able to initialise your value object based on almost anything that might represent its value.

# Making it so

So what do you think of this?

```ruby
class ISBN
  def initialize(value)
    @isbn_string = if value.is_a? String
                     value.gsub(/[^[:digit:]]/,"")
                   elsif value.is_a? Integer
                     value.to_s
                   elsif value.is_a? NilClass
                     ""
                   end
  end
end
```

Pretty bad.

One of the principles of object-oriented design that you might pick up along the way is that this sort of code is A Bad Thing, because we're not supposed to care about what type/class an object is, only about what it can respond to.

Now if we were sending the a message `#address` to an object, we'd be fine – we'd just expect the Person/Company/Owner/Invoice etc classes to implement that method, and because they are our own domain objects and we write the code for them, we can define whatever behaviour we want on them.

But what do we do when the object is one of the Ruby core classes? `String`, `Integer`, or even `NilClass`?

One answer is to monkey patch, but by now I think we all know that that is Also A Bad Thing and that we should instead be implementing a refinement.

How about this?

```ruby
module ISBNInitializerExtensions
  refine String do
    def to_isbn_string
      gsub(/[^[:digit:]]/,"")
    end
  end

  refine Integer do
    def to_isbn_string
      to_s
    end
  end
     
  refine NilClass do
    def to_isbn_string
      ""
    end
  end
end
```

It's not too bad, as the ISBN initialisation then becomes:

```ruby
class ISBN
  using ISBNInitializerExtensions
  def initialize(value)
    @isbn_string = value.to_isbn_string
  end

  def etc
  end
end
```

We can scatter `ISBN.new(obj)` around the system and the ISBN class doesn't need insights into the cleansing for a string or a number. It assumes that the input value has implemented it.

But we don't do `Integer.new("78")` very much, do we? We like `"78".to_i` instead. (OK, we can do the explicit method, but only when we need the specific behaviour that that brings with it. Otherwise we just send `#to_i`).

We also need to know the name of the class that implements the ISBN object, and how to initialise it, but maybe that's not such a big deal.

Taking it a little further, what about:

```ruby
module ISBNInitializerExtensions
  refine String do
    def to_isbn
      ISBN.new(gsub(/[^[:digit:]]/,""))
    end
  end

  refine Integer do
    def to_isbn
      ISBN.new(to_s)
    end
  end
     
  refine NilClass do
    def to_isbn
      ISBN.new("")
    end
  end
end
```

This module becomes the location in the system where we convert core Ruby classes to the application-defined class ISBN, and the only place where we need to do refactoring if we wanted to change the name of the `ISBN` class.

And we could implement similar logic for `#to_zip_code`, `#to_country`, `#to_currency`, etc., with similar benefits.

And then, secure in the knowledge that the classes which might need to return it have already implemented the clean-up, we can write:

```ruby
class ISBN
  def initialize(value)
    @isbn_string = value
  end

  def etc
  end
end
```

And we're back where we started, with a nice, clean initialisation and the ability to:

```ruby
  "978-3-16-148410-0".to_isbn
  9783161484100.to_isbn
  nil.to_isbn
```

.. as long as we have first added `using ISBNInitializerExtensions` to the class or module where we want to use it (which admittedly seems to make it tricky in Rails views for some reason).

For completeness, we can ...

```ruby
class ISBN
  def initialize(value)
    @isbn_string = value
  end

  def to_isbn
    self
  end

  def to_s
    @isbn_string
  end
end
```

This does make value object definitions in Rails a thing of very little code:

```ruby
class Book
  using ISBNInitializerExtensions

  def isbn
    @_isbn ||= self[:isbn].to_isbn
  end

  def isbn=(obj)
    self[:isbn] = obj.to_isbn.to_s
  end
end
```

# Summary

So there are two options here:

1. Refine the clean-up of the parameters, taking them out of the initialiser, and letting us do `ISBN.new(obj)` for fairly arbitrary classes of obj.
2. Refine the complete transformation, allowing `obj.to_isbn` within any class or module where we have invoked the use of the appropriate refinement.

One further point: to more explicitly hint that this is our application's refinement on the behaviour of the object, would it be wise to have a naming convention that uses "as" instead of "to", for example `9783161484100.as_isbn`? At a push you could claim that the "as" is an acronym for "application specific", if you really wanted ...