On October 17, 2012, Bryan Helmkamp, founder of Code Climate, wrote a blog post outlining 7 patterns to refactor fat ActiveRecord models in Ruby on Rails. Here at Crush & Lovely, this post is a core reference for all Rails developers on how to separate concerns, write modular, concise and expressive code, and make testing exceedingly simple.
But Value Objects offer a great place for domain logic to reside. Almost every value in your application has logic associated with it, such as equality, and the best place for that logic is on an instance of a value object.
Consider a student grading application, where students aggregate percentage scores are used to assign letter grades and determine whether or not the student is passing or is improving.
This has the added benefit of making your code base much more expressive, allowing you to write code such as:
A couple things to note about integrating Value Objects into an application:
- The valueOf and toString methods have special purposes in the ECMAScript specification, and are suggested for all custom Value Objects. Using the above Grade object, we have enabled it for standard ECMAScript syntax using the valueOf method we defined, giving us:
Even if two separate objects return the same value from valueOf, they will still not evaluate as equal using ===:
For converting your value object using JSON.stringify, the convention is to specify a method toJSON that returns the value you want stringified. If no toJSON method is specified, JSON.stringify will evaluate the valueOf method. If no valueOf method is defined, the object will evaluate as an object, which is almost certainly undesired.
It is a good pattern to have the valueOf method return the same value that the object was initialized with, so that you can rebuild the object on the other end of the transport. This is particularly useful if the application has both a client-side and server-side application and share Value Objects. If you have the input and output use the same value, you can work with a Value Object on the server-side, send down the value to the client using valueOf, and then rebuild it on the client-side again.
If you prefer a more functional programming approach to Value Objects, you can add methods to the constructor function instead of the prototype. Consider the following example:
Both the object-oriented approach and the functional approach are valid, it just depends on your particular style.
Because this pattern centralizes logic into a single object, testing becomes much easier and quicker, and allows a small suite of tests to cover a lot of application logic. Consider the following tests:
One benefit of testing a value object like this is that the setup for testing couldn't be easier. Testing multiple permutations is quick and efficient, allowing you to avoid building fake models or writing complex logic. Additionally, the logic is isolated from the any model tests, so the test suites are smaller and more focused.
In the next post, we'll take a look at Service Objects, which are great tools for isolating procedural code.