Value Objects in Rails
You can get a lot of value in rails by using value objects
You can get a lot of value in rails by using value objects to compose your ActiveRecord models and using concepts from value objects to limit said models.
I will strive to explain here:
What is a value object
How to make a pure value object and use it in rails
How to transform ActiveRecord objects into almost value objects
Why and when could you use value objects
What is a value object
I deliberately choose not to look up a definition (in part so you, dear reader, can tell me how wrong I am) and provide the definition I have in my head.
A value object is an object that does not need nor have an identity, that can be substituted by another instance of the same object with the same attributes and that is immutable (as changing it would make it by definition another object)
So if we imagine a Money
class, that implements a #value
and a #currency
attributes. Any <Money value=5 currency=€>
Can be substituted by another instance of Money
that has the same value and currency (after all, when you get a 5€ not, you do not care about the serial number).
This can be opposed to an Account
object, that has #balance
and #currency
methods, in this case, substituting an account for another might mean allowing someone to withdraw money that isn't theirs.
How to make 'pure' value objects and use them in Rails
At its simplest, a value object is just a class with an overridden ==
method (well and ===
). You could just do:
class Money
attr_reader :amount, currency
def initialize(amount:, currency:)
@amount = amount
@currency = currency
end
def ==(other)
amount == other.amount &&
currency == other.currency
end
end
This is a value object in and off itself, but how do you plug it into your ActiveRecord object?
Taking advantage of the composed_of
macro:
class Accoun < ActiveRecord
[...]
composed_of :money, mapping: { balance: :amount, currency: :currency }
end
How to transform ActiveRecord objects into almost value objects
Sometimes, we need our ActiveRecord objects to behave with certain characteristics of value objects, in this case, I will show you how to make an active record (almost) immutable:
class CarModel
[...]
def read_only?
!persisted?
end
end
You could go further and also overwrite the ==
method if you wanted.
Why and when could you use value objects
All of this is well and good, but I gather part of the reason you are here, cherished reader, is to know when and why you would use value objects.
When
As stated previously, one of the key reasons to use value objects is when you only care about the characteristics.
You might also find yourself, attentive reader, with 'prefixed attributes'. A classic example of this might be address
For example, a Person
had several related attributes like: address_street
, address_number
, etc
You can group all of these in an address object and enrich them with methods such as #full_address
and store all of those in a jsonb
address
column.
Finally, it helps you break god objects into more cohesive and reusable units
Why
It is safer as having these objects being immutable means they won't change in the course of your code, which is one less variable to take into account.
You can unit test these objects with more ease than a typical ActiveRecord object.
Finally, it allows you to give them larger behavior than you could if you represented the values 'only' with a primitive.