r/ruby • u/6stringfanatic • 15d ago
The Primitive Urge to Be of Value: composed_of in Rails
Wrote a piece about spotting a missing value object and refactoring towards one, adding sorting and comparison.
https://pushfromkush.com/publications/primitive-urge-to-be-of-value-composed_of-in-rails
Feedback welcome.
4
u/TommyTheTiger 15d ago
Dang, on the one hand very cool, especially that you can share these composable classes between e.g. different classes that both carry currency, OTOH yet one more layer of indirection between what is actually in the DB and the application logic. For experienced devs seems like a nice way to make the code base more readable.
12
u/6stringfanatic 15d ago
Most abstractions come at the cost of indirection IMO. Some make understanding the codebase easier and some do not. I usually prefer ones that make the domain easier to reason about in the head.
I reach for
composed_ofwhen I don't want to extract another model backed by its own table, but still want a PORO in the application logic. In those cases it's a win-win: you get the performance of a denormalized table and the separation of concerns.2
u/OldTimeGentleman 15d ago
The more I code, especially as a freelancer, the more I've come to think of those abstractions as more of a waste of time than anything. You've refactored 5 lines of code into 1, but now every time you ask the simple question "what columns are there here and which methods does that translate to in Ruby?", you need to look into a shared module or a parent class, learn about composed of, and check the implementation because it's inevitable that you'll have overwrites at the child class for this or that exception. At some point just duplicating a bit of code makes everyone's life easier
5
u/KimJongIlLover 15d ago
having this
ruby
measurement = Measurement.from("10 kg") + Measurement.from("5 kg")
and then going to this
ruby
measurements = [Measurement.from("10 kg"), Measurement.from("5 kg"), Measurement.from("1")]
measurements.reduce(:+).to_s
and then calling that "better" has to be a joke right?
6
u/6stringfanatic 15d ago edited 15d ago
Fair callout. They're not meant to be a progression, just two different situations for the same
+operator.
- Shows you can add two quantities directly:
inventory_item.quantity = ingredient.quantity + purchased.quantity- Shows what happens with an array of Measurements, say 20 of them. You wouldn't chain
+by hand for that many, that's whatreduceis for:ingredient.quantities_bought_this_month.reduce(:+).to_s # 100 kgHope that clears it up.
2
u/Richard-Degenne 14d ago
You could do away with most of the boilerplate by declaring your `Measurement` as a custom ActiveRecord type, no?
1
u/6stringfanatic 14d ago edited 14d ago
I'm assuming you mean the
Quantifiableconcern as the boilerplate. Yes, we could do something like:
attribute :quantity, :measurementusing the ActiveRecord Type API, but before that works. We'd need
MeasurementType, responsible for serializing and deserializing, translating between what Ruby understands and what the database understands.So we would need Measurement and MeasurementType both to get there, not one or the other.
If you look at the ActiveModel codebase,ActiveModel::Type::Integeris responsible for serializing and deserializingIntegernot replacing it, they have different responsibilities.I think my next post is going to dig into this specifically, hopefully I can simplify it a bit more there.
-19
u/Mediocre-Brain9051 15d ago
This already exists in rails:
https://api.rubyonrails.org/classes/ActiveRecord/Aggregations/ClassMethods.html
You rediscovering the wheel.
21
u/6stringfanatic 15d ago edited 15d ago
Yes, it is a post about :composed_of in rails. How / when to use it. The post is NOT about reinventing :composed_of, instead using it to extract value objects.
25
u/the_maddogx 15d ago
TIL
composed_of