When I started writing the ORM part of MassiveRecord I decided it would be smart to take a tiny winy peak into the code of both ActiveRecord and Mongoid. After all; I had never written an ORM before and it would be really stupid not to take a bit of inspiration from a couple of the well known ORMs. Not only to get inspiration, but also to use same naming and patterns where it made sense so people would feel at home entering MassiveRecord.
I want to share with you a little secret I came across writing MassiveRecord. Its not really that hard! All the free and cool stuff we take for granted, like callbacks, validations, dirty state of attributes, finder methods and persistence - it's all neatly packed up and included into the base of an ORM class. How is it all glued together? What happens when you have a person and asks it to be saved?
Lets keep it simple for a while.
You can't get it any simpler than that. Now, we want our ORM base class to be able to do some operations related to persistence, so well extend our class with this:
Cool! So, let's jump into our dedicated module for persistence and do some magic!
Our ORM can now save and destroy records. It can also delegate to create or update based on the current state of the record. But how can we make our ORM cancel the call to save if the record is invalid? We need someone responsible for validations!
..And we do need to implement it!
That didn't look too complicated, and actually, thanks to ActiveModel, that is more or less the complete validation module which MassiveRecord currently uses. When you call person.save that save call will first hit the save method defined in the Validation module (as it is included last). It takes care of performing validation and based on if that person is valid or not it either returns false right away, or calls super to let a save method "up-the-chain" handle it. And where do we end up? In the persistence module's save which takes care of saving the record now that we know it litterly passed validation.
Saving a new record will persist all of it's attributes to the database, but when we have a persisted record in the database we can keep track of which attributes has changed or not. And, when we know that, we can update only what we need when we call save on a persisted record. So, lets extend our base class one more time:
The next thing we'll need to do is to make our self the Dirty module.
This dirty module is a bit simplified from the one in MassiveRecord. There are two things worth noticing here.
First, lets take a look at save. The save method is defined here as well, but it simply forwards the call to super (Persistence module) without intercepting the call. However, when the call returns it will, based on it's success or not, reset the dirty state.
Second, the update method is overriden here. This method is being called by the persistence module when it decided the record should be updated. When the private update method is being called it begins at the bottom of the chain again, going through the dirty module. The dirty module takes a quick look at the record's changes. If it is empty it simply returns true and we will actually never do a database call (as we don't call update in the persistence module). If it is not empty it calls the update method in persistence module and injects a list of attribute names in the arguments list which should be updated.
All of this is pretty neat; keeping conserns tucked together in cute modules. We are also benefitting keeping methods small and simple as it makes it easy to hook into the call chain. And another good point is that you can remove the dirty module without breaking or affecting the records' ability to persist.
I hope you found it interesting, even though this was a simplified and limited example!