Ruby 3.1 adds a new core class called Data to represent simple immutable value objects. The Data class helps define simple classes for value-alike objects that can be extended with custom methods.
While the Data class is not meant to be used directly, it can be used as a base class for creating custom value objects. The Data class is similar to Struct, but the key difference being that it is immutable.
Discussions started in Ruby forums to build a data model on the principles of the Value Object, introduced by Martin Fowler. It has the following properties:
Compared by type & value
Let’s look at an example of defining a Data class that represents a person.
Data.define takes a list of symbols and returns a new class. Each symbol is an member of the newly defined Person class.
Now let’s use the Person class to create a new person object.
However remember that the Person class is immutable. Let’s try to change the name of the person.
This is the main difference between Data and Struct. Struct is mutable, while Data is immutable.
Let’s define the same Person class using Struct to see the difference.
However there is one caveat. If some of the data members are of a mutable class, Data does no additional immutability enforcement.
Ruby maintainers decided that Struct could not be made to suit the requirements of a value object. The core Struct class is a “somewhat alike” value-object; it is compared by value and is,
collection-alike (defines to_a and is Enumerable)
dictionary-alike (has  and .values methods)
Let’s look at this in action.
The expectation for the struct object is that it forms an array of structs, however the struct object is deconstructed and its values are used to form the Array. However the data object returns an array of Data objects. This is because the Data class is not Enumerable.
This also means that the Data class will not inherit the Enumerable module, which provides methods like #include?, #count, #map, #select and #uniq, amongst others. It is truly a simple value object. It is only meant to be a storage for immutable atomic values.
Further, the struct object is also dictionary-alike, which means that it can be accessed using the .values method. However the Data object is not dictionary-alike.
However, it is worth noting that the Data class does have a #to_h method.
Finally, it is possible to extend the Person class with additional methods, only during class definition.
Now let’s compare the two Person objects.
The new Data class is a simple value object. While it is not meant to be a replacement for Struct, it can be used to store immutable atomic values making it easier for Ruby developers to create simple structures to store immutable data.
Will you be using the Data class in your next project? Let us know!