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.
When a piece of business logic associated with a model becomes sufficiently complex or is not a part of the core model logic, it is a candidate for extraction into a Policy Object. These objects encapsulate operations that interpret models and exclusively return boolean values, describing whether the policy passes or doesn't pass the object.
For example, if a worker process sends an email to a group of users, a Policy Object can be used to determine which users should be emailed. A Policy Object would return true if the user has an email address, that email address has been verified, and they have not unsubscribed from correspondence.
Helmkamp describes the potential overlap in concept between a Policy Object and a Query Object or a Service Object. He writes, "Policy Objects are similar to Service Objects, but I use the term 'Service Object' for write operations and 'Policy Object' for reads. They are also similar to Query Objects, but Query Objects focus on executing SQL to return a result set, whereas Policy Objects operate on domain models already loaded into memory."
Let's imagine a collection of students that comprise the entire freshman student body. We want to filter out all students that are eligible to register for sports, which we define with these rules:
- Not suspended
- Not expelled
- Are passing all classes
Eligibility for sports enrollment isn't a core concept of the Student model and the logic is complex, so we can extract it into a Policy Object like this:
You can also see the composition opportunities for Policy Objects in the #isPassing method, which uses another Policy Object to return whether or not the student is passing. Now policies that are part of our business logic are extracted from the model itself and put into unit-testable, easy to understand, and composable components.
Unit testing a Policy Object couldn't be simpler -- one should build an object that should either pass or fail and ensure that the Policy Object returns the right boolean value.
In the next and final post of this series, we'll take a look at Decorators, which are great for composing complex and varying processes.