7 Patterns to Refactor JavaScript Applications: Value Objects

Facebook
Twitter
LinkedIn
Note: This is part one (next) of a seven part series.

On October 17, 2012, Bryan Helmkamp, founder of Code Climate, wrote a blog post outlining seven patterns to refactor fat ActiveRecord models in Ruby on Rails. This post is the first of a seven part series that will demonstrate these concepts in the JavaScript environment. I will show that they are just as applicable, and equally as valuable. Each post will cover one of the seven patterns in detail. In this post we’ll be talking about Value Objects.

Bryan’s article describes Value Objects as “simple objects whose equality is dependent on their value rather than an identity.”

Because JavaScript adheres to the “pass-by-reference” principle for all JavaScript objects, there are no native examples of this in ECMAScript 5 or even Harmony, save for primitives.

For example:

// Primitives
var foo = 2;
var bar = 2;
foo === bar; // => true

// Object identity
var foo = new Number(2);
var bar = new Number(2);
foo === bar; // => false

The first example assigns primitive integers to the variables foo and bar, which are equal by their value, even though primitives are technically still objects in JavaScript. The Number constructor, even though it provides a wrapper for primitives, is a Plain Old JavaScript Object, and is therefore equal by reference, not value. Thus, in the second example, foo does not equal bar, even though both Number instances represent the same integer value.

But Value Objects offer a great place for domain logic to reside. Almost every value type in your application has logic associated with it, such as equality, and the best place for that logic is in a value object.

Example

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.

var _ = require('underscore');

var Grade = function(percentage) {
  this.percentage = percentage;
  this.grade = this.grade(percentage); 
};

Grade.prototype = _.extend(Grade.prototype, {

  passingGradeLetters: function() {
    return _.chain(Grade.grades).where({passing: true}).pluck('letter').value();
  },

  grade: function(percentage) {
    return _.find(Grade.grades, function(grade) { 
      return percentage >= grade.minimumPercentage; 
    });
  },

  letterGrade: function() {
    return this.grade.letter;
  },

  isPassing: function() {
    return this.grade.passing
  },

  isImprovementFrom: function(grade) {
    return this.isBetterThan(grade);
  },

  isBetterThan: function(grade) {
    return this.percentage > grade.percentage;
  },

  valueOf: function() {
    return this.percentage;
  }

});

Grade.grades = [
  {letter: 'A', minimumPercentage: 0.9, passing: true},
  {letter: 'B', minimumPercentage: 0.8, passing: true},
  {letter: 'C', minimumPercentage: 0.7, passing: true},
  {letter: 'D', minimumPercentage: 0.6, passing: true},
  {letter: 'F', minimumPercentage: ,   passing: false}
];

module.exports = Grade;

Using a Value Object has the added benefit of making your code base much more expressive, allowing you two write code such as:

var firstStudent = {grade: new Grade(0.45)};
var secondStudent = {grade: new Grade(0.70)};

firstStudent.grade.isPassing() //=> false
firstStudent.grade.isBetterThan(secondStudent.grade); //=> false

There are a couple things to note about integrating Value Objects into an application.

You might also like:   Setting Up A Client-Side JavaScript Project With Gulp And Browserify

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:

var myGrade = new Grade(0.65);
alert('My Grade is ' + myGrade + '!'); // alerts, 'My Grade is 0.65!'

var myOtherGrade = new Grade(0.75);
myGrade < myOtherGrade; // true

Even if two separate objects return the same value from valueOf, they will still not evaluate as equal using ===, for example:

var myGrade = new Grade(0.65);
var myOtherGrade = new Grade(0.65);
myGrade === myOtherGrade; // false

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:

Grade.equal = function(grade1, grade2) {
  return grade1.valueOf() === grade2.valueOf();
}

var myFirstGrade = new Grade(0.7);
var mySecondGrade = new Grade(0.7);
Grade.equal(myFirstGrade, mySecondGrade) // => true

Both the object-oriented approach and the functional approach are valid. It just depends on your particular style.

You might also like:   Inline Styles: Yes or No?

Testing

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:

var Grade = require('./grade');
var grade1;
var grade2;

describe('Grade', function() {

  describe('#isPassing', function() {

    it('returns true if grade is passing', function() {
      grade1 = new Grade(0.8);
      expect(grade1.isPassing()).to.be.true;
    });

    it('returns false if grade is not passing', function() {
      grade1 = new Grade(0.58);
      expect(grade1.isPassing()).to.be.false;
    })

  });

  describe('#isImprovementFrom', function() {

    it('returns true if grade is better than comparison grade', function() {
      grade1 = new Grade(0.8);
      grade2 = new Grade(0.7);
      expect(grade1.isImprovementFrom( grade2 )).to.be.true;
    });

    it('returns false if grades are equal', function() {
      grade1 = new Grade(0.7);
      grade2 = new Grade(0.7);
      expect(grade1.isImprovementFrom( grade2 )).to.be.false;
    });

  });

  describe('#isBetterThan', function(){

    it('returns true if grade is better than comparison grade', function() {
      grade1 = new Grade(0.8);
      grade2 = new Grade(0.7);
      expect(grade1.isImprovementFrom( grade2 )).to.be.true;
    });

    it('returns false if grades are equal', function() {
      grade1 = new Grade(0.7);
      grade2 = new Grade(0.7);
      expect(grade1.isImprovementFrom( grade2 )).to.be.false;
    });

  });

});

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 any model tests so the test suites are smaller and more focused.

Conclusion

Value Objects are great tools for defining your domain logic and creating more expressive code. Even if you have few or no custom methods on a value object, there is still utility in defining and using value objects. Plus, you never know when you’ll need to extend a value type’s functionality, and when you do, you’ll have just the place to do it.

In the next post, we’ll take a look at Service Objects: tools for isolating procedural code.

The post was originally written for the Crush & Lovely Blog in 2012 and has been lovingly brought up-to-date for Engine Yard by Michael Phillips, the original author. Special thanks to Justin Reidy and Anisha Vasandani for help with the original.

Want more posts like this?

What you should do now:

Facebook
Twitter
LinkedIn

Easy Application Deployment to AWS

Focus on development, not on managing infrastructure

Deploying, running and managing your Ruby on Rails app is taking away precious resources? Engine Yard takes the operational overhead out of the equation, so you can keep innovating.

  • Fully-managed Ruby DevOps
  • Easy to use, Git Push deployment
  • Auto scaling, boost performance
  • Private, fully-configured Kubernetes cluster
  • Linear pricing that scales, no surprises
  • Decades of Ruby and AWS experience

14 day trial. No credit card required.

Sign Up for Engine Yard

14 day trial. No credit card required.

Book a Demo