Ruby `Data` Class – A Convenient Way to Create Value Objects
Ruby’s Data
class was introduced in Ruby 3.2, offering a convenient way to define value objects. The concept of value objects was popularized by Martin Fowler and Eric Evans through their books and articles. In the real world, we often represent properties like coordinates on a map (x, y)
or the speed of a car—using primitives such as integers or strings. While these work, they lack the semantic clarity and behavior of custom types. This is where value objects shine. They encapsulate meaning and behavior around a set of values.
Ruby’s Data
class was created to provide a native way to represent such concepts. Struct does fullfil this requirement, except the part of immutability. Ruby Structs are mutable. Some example of use case for value objects are to represent money, email address, co-ordinates, etc.
Lets now explore ruby Data, keeping in my the characteristics of a value object as shared by Marin Fowler. A value object should have:
- No Identity
- Immutable
- Equality by Value
- Small and Simple
- Reusable
Let’s explore how Ruby’s Data
class supports these characteristics.
class Data
Data
is a core Ruby class, so no external gems are needed.
Here’s a simple example:
|
|
You can now initialize/use MarsRover
like this:
|
|
Create another instance:
|
|
These objects are immutable
|
|
and you can compare them
|
|
A More Practical Example
The Mars Rover example is abstract; here’s a more practical one using location data:
|
|
You don’t need to compare x
and y
individually—value comparison is built-in.
Updating Location with Immutability
You update the rover’s position by replacing the Location
object. This is exactly like how we would replace the integer
value x and y. But conceptually we are entering a new location which has x and y co-ordinates.
|
|
Making Data
Comparable
Ruby Data
instances support ==
and eql?
out of the box but not <
, >
, or between?
. To enable this, include the Comparable
module and define <=>
:
|
|
Now you can do:
|
|
This works with regular classes too:
|
|
Adding Validations
Ruby’s Data
does not support validation out of the box. However, you can override initialize
with keyword arguments to add it:
|
|
Bonus: Using dry-struct
For more complete value object features, consider using dry-struct
. It adds type coercion and validation.
|
|
Conclusion
Ruby’s Data
class makes it easy to define small, immutable, and comparable value objects with minimal effort. While it doesn’t support validations out of the box, it serves as a powerful and expressive tool for modeling semantics in your domain. When more control or strict typing is required, tools like dry-struct
fill in the gaps. Whether you’re modeling locations, money, or any other composite data, value objects bring clarity and correctness to your code.