Tuesday, May 1, 2012

Alternative naming for associations in MongoMapper

MongoMapper is a mature Object Relational Mapper written in Ruby and for use with MongoDB. It roughly follows the conventions used in Rails' ActiveRecord / ActiveModel. As my use of MongoDB has grown more sophisticated, I have had some trouble discovering how to get what I need done with MongoMapper. Fortunately, this is often not a deficiency of MongoMapper, it's more that I wasn't aware of all its features.

In general on my blog, I'll point out a few features that I felt were incompletely explained in the documentation. Here I address how to use alternative naming for associations.

Using non-default naming for associations

By default, associations are named after the class name:

class Plum
   include MongoMapper::Document
   one :pit
end
class Pit
  include MongoMapper::EmbeddedDocument
end


Running the following to create a Plum with a Pit:


p = Plum.new
p.build_pit
p.save


will result in a document in mongodb like this:

{
   "_id" : ObjectId("4f9ff50ba0b7f9b71500004a"),
   "pit" : {
      "_id" : ObjectId("4f9ff51ba0b7f9b71500004b")
   }
}

However, what if you want to have the class called Pit, but have the association name called dapit? Well, you can use the :class option when declaring the association:


class Pit; end

class Plum
  include MongoMapper::Document

  one :dapit, :class => Pit 
end

class Pit
  include MongoMapper::EmbeddedDocument
  embedded_in :plum
end


Notice that in the above, I needed to declare the class of the embedded document ahead of time, so that I could refer to it in the class definition of Plum.
With the above associations, you can do the following:


p = Plum.new
p.build_dapit
p.dapit.class.name
# => "Pit"


You can do the same with a non-embedded association:


class Pit; end

class Plum
  include MongoMapper::Document

  one :dapit, :class => Pit
end

class Pit
  include MongoMapper::Document
  belongs_to :plum
end


With this version you can do the following:


p = Plum.new
p.create_dapit
p.save
p.dapit.plum

However, what if you want to rename the belongs_to association back to plum? This is a bit more complicated. You need to also specify the foreign_key so that both directions of the association use the same key name for the ObjectID:


class Pit; end

class Plum
  include MongoMapper::Document

  one :dapit, :class => Pit, :foreign_key => :daplum_id
end

class Pit
  include MongoMapper::Document
  belongs_to :daplum, :class => Plum
end


You might wonder, why do I specify the foreign key daplum_id when I'm declaring the association to dapit. Well, for one associations, the ObjectID of the referring Plum is stored in the pit collection. Then when the pit is requested, MongoMapper find the pit document that points back to the plum. So the key name that needs to be changed to match da new naming convention is plum_id -> daplum_id and it's stored in the pit collection, but this renaming is declared in the Plum class, not the Pit class.

You can use the above version as follows:


p = Plum.new
p.create_dapit
p.save
p.dapit.daplum

And the document for a pit now looks like this, using the alternatively named key:

{
   "_id" : ObjectId("4fa0002aa0b7f9653800002b"),
   "daplum_id" : ObjectId("4fa00026a0b7f9653800002a")
}

Hope that all makes sense.