Value objects in Ruby
(or why does Ruby love nil so much)
19 Mar 2011
In the latest Destroy All Software screencast [1], Gary Bernhardt uses a Struct to represent a value object. There are a couple of problems with doing this, because of the way Structs are implemented.
Structs can be used to create a simple object that just holds values in named fields. However, the constructor can take less than the default number of arguments and set other fields as nil:
Point = Struct.new(:x,:y)
Point.new(1)
=> #<struct Point x=1, y=nil>
This can lead to accidental nils floating around your system, as well as you creating a value object with weird semantics. In this case, having a point without a y value probably doesn’t make any sense.
Furthermore, objects created by Struct are mutable. As a long time proponent of immutability wherever possible, I dislike this.
Enter Values
The problems with Struct looked pretty easy to solve with metaprogramming in ruby. So, I created a library for working with value objects as I want to: Values.
Values is used roughly the same way you use Struct (it doesn’t have all the features though):
Point = Value.new(:x,:y)
p = Point.new(1,0)
`=> #<Point:0x00000100943788 @x=0, @y=1>`
But they aren’t mutable:
p.x = 1
=> NoMethodError: undefined method x= for #<Point:0x00000100943788 @x=0, @y=1>
from (irb):6
from /usr/local/bin/irb:12:in <main>
They also can’t accidentally add nil into the constructor:
Point.new(1)
=> ArgumentError: wrong number of arguments, 1 for 2
from /Users/tcrayford/Projects/ruby/values/lib/values.rb:7:in `block (2 levels) in new
from (irb):5:in new
from (irb):5
from /usr/local/bin/irb:12:in `<main>
This library is really tiny (17 lines at the moment), and is has a good number of examples. Its probably not worth using unless you have quite a few dumb value objects in your system (as its effects can be easily replicated with plain ruby classes).
Notes
[1] Destroy All Software is a screencast series that teaches programmers about software design, UNIX and dynamic languages. Highly reccomended.
For reasons why nil is bad, see the example screencast there.