Cubes

Christian Fregnan

Consider Value Objects

In your applications, you’ll often find yourself creating a range of view helpers around the same concept, constructing complex calculation methods, or passing a value (or several) around multiple methods.

When this happens a simple object, smaller than an Active Record model, is trying to reveal itself.

In these cases, consider refactoring the functionality into a “Value Object”, as described by Martin Fowler:

A small simple object, like money or a date range, whose equality isn’t based on identity.

Instead of…

…just using a helper:

def css_color(r, g, b)
  "##{r.to_s(16)}#{g.to_s(16)}#{b.to_s(16)}"
end

Use…

…an extracted “Value Object” to truly capture the concept your application is using.

class RGBColor
  def initialize(red, green, blue)
    @red = [[0, red].max], 255].min
    @green = [[0, green].max], 255].min
    @blue = [[0, blue].max], 255].min
  end

  def to_hex
    [@red, @green, @blue].each_with_object("") do |part, to_hex|
      to_hex << part.to_s(16)
    end
  end
end

And in the helper

def css_color(r, g, b)
  "##{RGBColor.new(r, g, b).to_hex}"
end

Why?

Just because a concept in our app isn’t saved to the database doesn’t mean it shouldn’t be an object in our app.

Refactoring into a value object has several benefits since you isolate the code for this concept. You might hear this referred to as ‘separation of concerns’. You can also test the behaviour of your concept in isolation, leading to more comprehensive and efficient tests.

This improved organisation of your code will clarify your understanding. The code/concept is also easier to reuse and you gain a clear place for extending the functionality of the object.

Why not?

It might be too soon. Finding the correct time to extract a concept into a value object can be difficult. Too early leads to unnecessary complication and potentially increased confusion; too late and you’re already coding in a mess!

For implementation you could use a Struct or even OpenStruct rather than the class definition approach shown in the example, but these are mutable objects (you can change their properties) which can add complication, rather than simplify your code.

Last updated on July 28th, 2019