February 23, 2010

Posted by John

« older newer »

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.

Labels: Features

9 Comments

  1. 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!)

  2. 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!

  3. @Mathias: because the minute Rails starts caring about different external features id the point where rails loses its elegance and simplicity.

  4. 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.

  5. Hi, I wonder why do you repeat the key/value in the pull method ?

    Item.pull({:asset_ids => id}, {:asset_ids => id})
  6. @Dogz: Totally agree. Poorly drafted by me.

    But it would be great with a PostgreSQLMapper who put up PostgreSQL:: Document :-)

  7. @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. ?

  8. @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.

  9. finasteride
    http://propecia3424.realpillstablets.com – recommend this propecia

Sorry, comments are closed for this article to ease the burden of pruning spam.

About

Authored by John Nunemaker (Noo-neh-maker), a web developer and programmer who has fallen deeply in love with Mongo. More about John.

Syndication

Feed IconMongoTips Articles - An assortment of news, howto's and thoughts on MongoDB.