Introduction to Mutations
About a year ago we found ourselves with an overly complicated codebase. The amount of business logic is constantly growing. We followed the ‘fat model’ approach, meaning most of this business logic is contained in our models. Actions on our models can have many obscure side effects caused by ActiveRecord callbacks.
The list of business requirements grows continuously, so the number of actions on models is growing at a constant rate too. It is hard to keep a good overview of all callbacks that are executed when performing an action, this makes the codebase error prone. We needed a way to structure our code to ensure its long-term maintainability.
The solution came in the form of a Ruby gem named Mutations. Mutations offers a way to organize our business logic into separate “commands”. For example:
require "mutations" module Contacts class CreateContact < Mutations::Command required do string :first_name string :last_name end optional do boolean :receive_newsletter end def execute instance = Contact.new(first_name: first_name, last_name: last_name) if instance.save and receive_newsletter NewsLetters::SendWelcome.run!(contact: instance.id) end instance end end end
The example above allows us to create a contact using the following call:
Contacts::CreateContact.run!(first_name: "Money", last_name: "Bird")
As you can see in the first example, we’ve specified “required” and “optional” blocks. Mutations uses these blocks to automatically validate the input and to throw away anything that isn’t listed.
In doing so we’re effectively programming by contract. We’ve exposed a contract to which the caller must comply. If the input requirements are met the logic will be executed. If they aren’t the execution will halt, usually resulting in an error. In doing so errors will surface with a clear message just after their inception. Without Mutations this wouldn’t be the case.
Mutations helped restructure our codebase, it neatly organized our business logic into commands. It ridded our models of unwanted hooks and made our controllers a lot leaner.