Forging Forgecraft: A Hybrid SQL MongoDB Data Solution
One of my primary goals while building Forgecraft is to learn as many new technologies as possible. I’ve learned the hard way that it’s better to leave this kind of exploration in your hobby projects and out of your production “for real” stuff. And, seeing as how there are so many emergent technologies right now in web development there’s a lot to explore!
Whole Hog is Too Much Hog
MongoDB has quite a bit of steam behind it in the Rails/Ruby community (and plenty of other places too), and with projects like Mongoid and MongoMapper (I went with Mongoid) it’s an easy drop-in replacement for Active Record that maintains all those ORM conventions you know and love. I decided against the replacement approach for one primary reason: I wanted to use existing, robust, and actively developed libraries that rely on Active Record.
Example: There’s no reason to roll your own authentication/user system when there are insanely popular and feature-full libraries already in place for them like Devise and authlogic (I went with Devise).
Fortunately combining Active Record with Mongoid and getting the best of both worlds proved to be easy and painless.
The Set Up
The instructions for Mongoid include a step to remove the Active Record libraries from being loaded and delete your database.yml. Simply skip that step and both systems will run in conjunction.
One small operational change you’ll have to make during development is explicitly defining when your rails generators should use Active Record. If you want to make an AR-driven model, your generators will look like:
rails g active_record:model Player … produces a model the extends from ActiveRecord::Base.
And by default, the data generators will use Mongoid:
rails g model Skill … produces a model that includes Mongoid::Document.
Here’s how this combo system really shines in my opinion: get all the great features of gems built on AR with all the schema less features of Mongo. I’ll walk through an example:
In Forgecraft, the authenticating object is the
Player and players have many
Skills which, right now, are Accuracy, Craftsmanship, and Perception. But, the skill list will likely grow and change over time as the game evolves. A typical AR/SQL based approach would be to create a skills table and a join table between players and skills resulting in a complex multi-table query to get a player’s complete skills.
It’s clear here that the Player and Skills would fit well together as a single document in Mongo, queried all at once in one tidy object. But, since the Player is also our authentication model, using Mongo would prevent us from using Devise to handle all of that boring and complex auth stuff for us.
Enter Hybridization. Forgecraft’s implementation uses the typical Devise set up around the Player object (again all on top of AR). All of a player’s skills go into a single document with a reference to the player, like so:
To set up the relationships like one would expect with Active Record is a simple method on your Player object:
Bam! Now we can retrieve our skills as a single document (fast), and add new skills with ease (easy). Since Mongo is schema less, you could even have a different set of skills per player. Again, all of this is achievable through SQL — it’s been done for decades — but it just makes more conceptual sense to treat these as a single document.
Querying and working with these objects and their relationships looks and feels just like active record:
player.skills player.skills.accuracy player.skills.update_attributes(:accuracy => 5) player.skills.inc(:accuracy, 1)
Benefits Outweight the Consequences
I know, I know — any time you introduce an additional system, especially a datastore, you introduce a lot of complexity, failure modes, testing, etc. But, I honestly think the benefits are worth this extra complexity. Speed, ease of development, and the potential for less schema-driven growth make this an ideal environment for me. I highly recommend it, and will be using it in the future!
More Forging Forgecraft: