How to create value objects in Ruby – the idiomatic way
allaboutcoding.ghinda.com
allaboutcoding.ghinda.com
To summarize, Data became available a few years ago in Ruby 3.2. You can create value objects by subclassing the Data class with Data.define. Data objects are immutable, comparable and easily greppable. They are constrained in ways Struct, Hash and Class are not. The shorthand removes boilerplate and the constraints create the utility.
Measure = Data.define(:amount, :unit) weight = Measure.new(amount: 50, unit: 'kg')
Also, if you’re writing a custom data class, you’d need to write code to support immutability which this article’s approach handles
The idea of distinct “concrete” and “dynamic” classes seems to be a product of a wrong mental model of how Ruby is executed.
There is no effectively no difference between these:
class Foo < Bar
attr_accessor :baz
end
Foo = Class.new(Bar) do
define_method(:baz) do
@baz
end
endJust not in quite the same way.
You are literally imagining a distinction that does not exist in Ruby.
Point = Data.new(:x, :y) do
def len
Math.sqrt(x**2 + y**2)
end
end
For one, Struct has existed since basically the beginning of time and works exactly the same way (sans the immutability and some nice auto-implemented features).If I see a data class instantiation. I immediately have an idea of its scope and what could/couldnt go wrong with it.
D = Data.define do E = new end
which is wrong since ’E‘ escapes (it becomes ´Object::E´)
also, I agree that its kinda weird to use Ractor API but for us (service object gem) it was the best way to check if an argument is immutable (via Ractor.shareable?)
I just found this[1] great article by Miko Dagatan with loads of benchmarks for Ruby 3.4.2 that goes into a bit of depth.
1. https://reinteractive.com/articles/ruby-performance-structs-...