Array Keys Allow For Modeling Simplicity
To silence the critics before they start, I will divulge that this feature could have been added with any database. Gasp! What I love is how easy Mongo made it.
For a few weeks now, we have been wanting to add some kind asset integration on the content edit form. I wanted any asset to be linkable to any item (items are pages, blogs, etc.).
Thinking Relationally
Given that Harmony was powered by a relational database, say MySQL, I would have created a new join table, probably related_assets. It would have a column for the asset_id and the item_id (and probably an incrementing id). Then, I would have created a model for this (RelatedAsset).
I would have migrated, prepped my test database, tested the model and added all the relationships like Item has_many :related_assets and has_many :assets, :through => :related_assets (and also reciprocal relationships on the Asset model.
At this point I would be several lines of code and test code in. Also, I would have to block my database for a bit while migrating in production, etc, etc. Lets switch over to awesome and see how I actually did it with Mongo.
Throw Off Them There Shackles
Knowing that the majority of the time I would be wanting to get the assets for an item (instead of the items for an asset), I added an asset_ids Array key on Item. Using MongoMapper, I can then declare my assets association to work through that key like this (more on in array association):
class Item
include MongoMapper::Document
key :asset_ids, Array, :index => true
many :assets, :in => :asset_ids
end
No join model needed. No migrations or prepping my test database. I simply added a key to store some related information and I was good to go. The only other piece I did was add an after_destroy callback to asset to pull itself out of all the item asset_ids keys.
class Asset
include MongoMapper::Document
after_destroy :remove_from_items
private
def remove_from_items
Item.pull({:asset_ids => id}, {:asset_ids => id})
end
end
pull
is a class method that MongoMapper adds to apply the Mongo $pull operator which you can read more about on their site.
Querying in Array Keys
Really for our purposes here, there was no need to index the asset_ids key as I am not querying by it at all. The reason I did is because eventually, we are going to show what items an asset is being used on from the assets area. This means we will need to query for an asset from the Item asset_ids key, so indexing is important. An example of how to do this would be:
# MongoMapper
Item.all(:asset_ids => asset.id)
# Ruby Driver
collection.find({:asset_ids => asset.id})
# Mongo Shell
db.items.find({asset_ids: '...'})
These examples would use the index on asset_ids to efficiently find all items that an asset has been linked to.
Less Code, Less Friction
The fact that we wrote most of Harmony using MySQL and then switched to Mongo makes me feel pretty confident that this feature would have been at least twice, maybe three times as much code. It might have been enough more of a pain, that we would have waited longer to do it, or even worse, wrote it off.
This is why I think Mongo is to databases what Rails was to frameworks. It removes even more friction between the steps of idea and implementation. You can read more about asset integration on the Harmony blog if you so choose.
9 Comments
Feb 23, 2010
Nice article, John. I think that array keys are one of MongoDB’s best features, as they obviate the need for a whole host of pesky join tables, as you describe.
(great comment form, btw!)
Feb 23, 2010
I have wondered many times why ruby, rails or something else never use the full power of the database.
For example PostgreSQL have all kinds of features like arrays =>
http://www.postgresql.org/docs/8.0/interactive/arrays.html
But few drivers/frameworks use them. Its really cool to see drivers/wrappers that use all the futures/muscles of the database.
Nice writeup John!
Feb 23, 2010
@Mathias: because the minute Rails starts caring about different external features id the point where rails loses its elegance and simplicity.
Feb 24, 2010
Any advantage to storing the reciprocal relationship in an Array in Asset as well? Something like:
class Asset
key :item_ids, Array
many :Items, :in => :item_ids
end
I don’t have an intuitive sense of whether the additional overhead of storing this data and needing to update both objects when adding or removing is cheaper than an index on asset_id.
Feb 24, 2010
Hi, I wonder why do you repeat the key/value in the pull method ?
Feb 24, 2010
@Dogz: Totally agree. Poorly drafted by me.
But it would be great with a PostgreSQLMapper who put up PostgreSQL:: Document :-)
Feb 24, 2010
@Jeremy: I think the first pair is probably the find predicate (i.e. find items that have a particular ID in their asset_ids array) and the second pair is what to pull (i.e. remove the specified ID from the asset_ids array).
I’m new to Rails and MongoDB and Linux and all this fun stuff. Do Rails peeps have in depth discussions about singular vs plural naming conventions for tables, collections etc. ?
Feb 24, 2010
@Jeremy – The first is what documents should be updated and the second is what operation. Normally it is with an id or something in the first, but in this case we just want to remove the document.
May 18, 2012
finasteride
http://propecia3424.realpillstablets.com – recommend this propecia
Sorry, comments are closed for this article to ease the burden of pruning spam.