ValueObjects in Rails ActiveRecord

ValueObjects in Rails ActiveRecord
Value Objects are a really useful pattern, but in my opinion used way too seldom. They let you reference objects rather by value than by identity. This blog post describes how to use Value Objects backed by Rails' ActiveRecord.

Value Objects provide a lot of benefits. For example, encapsulating domain knowledge in such objects may enrich and simplify your domain model at the same time. Their inherent immutability and comparability can make it much easier to reason about your code. Furthermore, they provide a decent home for additional getter methods that are purely based on its own attributes. If you want to learn more about Value Objects and their applications, I recommend reading about Domain-Driven Design (DDD), for example in Eric Evans influential Book.

Along this post I’ll describe three simple techniques to integrate Value Objects with ActiveRecord models:

  1. Using plain old Ruby accessors
  2. Using ActiveRecord’s composed_of
  3. Using a JSON column to back the Value Object’s data

We will apply these techniques to an ActiveRecord model called Invoice, which has a shipping_address as well as a billing_address. We have not yet decided how we will store the addresses in our database, but we want to represent them using a Value Object called Address:

If the Value Objects need to be comparable, some more boilerplate code is required, but this would exceed the scope of this article. For more information, see this great StackOverflow Answer.

1. Using Plain Old Ruby accessors

We’ll start with our Invoice ActiveRecord model using multiple database fields to store the shipping and billing address:

Using plain old ruby methods (getter and setter) we can easily introduce Value Objects to the Invoice model:

Now we can easily instantiate a new invoice using existing addresses, for example

Pretty nice, huh? Furthermore, we can send intention revealing messages to the invoice, like invoice.billing_address= that scream CHANGE BILLING ADDRESS.

Here are some more uses:

Instead of setting all attributes by hand or implementing a custom method changebillingaddress, we have a real object that we can pass around and contains it’s own behaviour/getters.

2. Using ActiveRecord composed_of

In the previous section you saw how to implement Value Objects in ActiveRecord by hand. But ActiveRecord already provides a neat helper composed_of. Here is the same implementation using composed_of

While this saves us some boilerplate code and provides some more useful configuration options (see composed_of), the first approach provides us more overall flexibility.

3. Using a JSON column

In some cases a model contains a list of Value Objects (like a list of addresses) or contains many different Value Objects (maybe depending on the type/state of the object). In such a case, we could store the data of all Value Objects in a single database json column instead of creating a lot of bloating columns for each single Value Object. I do not want to discuss database layout/normalisation here, but just want to demonstrate useful (maybe very pragmatic) techniques.

While this is pretty similar to the first approach, it gives us much more flexibility in storing the addresses. Instead of having a lot of flat attributes, we could store them in a nested way, e.g.  {shippingaddress: {street:, postalcode:, ...}, billingaddress: ...}. Then, we could use something like address.to_h  and Address.from_h to serialize/deserialize addresses into/from our data json blob. Furthermore, we can now (dynamically) add new Value Objects to an invoice without adding new database columns.

Summary

Use the Value Object pattern more often, it is a really handy one. It is intention revealing, compounds attributes and due to its immutability, it also guards against some nasty bugs. Some more examples of Value Objects are Money, Temperature  or  GPS , but it’s really up to you to identify them in your domain. I hope you can apply this pattern in your daily work and find it as helpful as I do.

Sie suchen den richtigen Partner für erfolgreiche Projekte?
Nehmen Sie Kontakt mit uns auf →