Mongo Mapper

Associations

MongoMapper allows you to define the relationship between your models. When you define an association, MongoMapper creates methods on your model that make it easy to create, break, find, and persist the connections between models in your application.

Defining Associations

Associations between models in MongoMapper are defined by combining the many, belongs_to, and one methods. These three methods can form a one-to-many, many-to-many, or one-to-one association.

One-to-Many

Use many and belongs_to in your models to form a one-to-many association.

class Tree
  include MongoMapper::Document
  many :birds
end

class Bird
  include MongoMapper::Document
  belongs_to :tree
end

In this one-to-many relationship, one tree can have many birds perching on it, and a bird can only be in one tree at a time.

Many-to-Many

In a relational database, you might model a many-to-many relationship by creating a “join table.” MongoDB doesn’t have joins. But because arrays are first class citizens in MongoDB, you can simply store an array of ObjectId’s.

Use an array key and many with the :in option in your model to form a many-to-many association.

class Book
  include MongoMapper::Document
  key :title
  key :author_ids, Array
  many :authors, :in => :author_ids
end

class Author
  include MongoMapper::Document
  key :name
end

Each book stores an array of the id’s of its authors in the author_ids key. This allows books to have many authors, and authors to have many books.

Currently, many-to-many associations are one-sided in MongoMapper. Support will be added in the near future for the inverse.

One-to-One

Use one and belongs_to in your models to form a one-to-one association.

class Employee
  include MongoMapper::Document
  key :name
  one :desk
end

class Desk
  include MongoMapper::Document
  key :color
  belongs_to :employee
end

In this one-to-one relations, an employee can only use one desk, and each desk can only be used by one employee.

Embedded Documents

When one document will almost always be fetched with another, it makes sense to just embed it into the same document. many and one associations can be used with embedded models. See EmbeddedDocument for more information.

Polymorphic Associations

MongoMapper lets you define polymorphic associations where one side of the association won’t always be the same class. The polymorphism can be done either the basic way (polymorphic documents in many different collections) or using single collection inheritance (polymorphic documents in one collection). See the examples below.

Note: All of these polymorphic examples use the many association, but MongoMapper’s polymorphism works fine with one in place of many.

Basic Polymorphism

Say you want users to be able to comment on articles and products:

class Article
  include MongoMapper::Document
  key :title
  many :comments, :as => :commentable
end

class Product
  include MongoMapper::Document
  key :sku
  many :comments, :as => :commentable
end

class Comment
  include MongoMapper::Document
  key :text
  belongs_to :commentable, :polymorphic => true
end

In the database, MongoMapper adds an extra commentable_type key so that it can fetch each comment’s parent from the proper collection.

In the database, a comment might look like this when converted to Ruby:

{
  "_id"  => BSON::ObjectId('...'),
  "text" => "It broke after a month.  But it was a good month.",
  "commentable_type" => "Product",
  "commentable_id"   => BSON::ObjectId('...')
}

Polymorphism with SCI

Polymorphism can also be provided by Single Collection Inheritance. The previous example could be rewritten:

class Commentable
  include MongoMapper::Document
  many :comments, :as => :commentable
end

class Article < Commentable
  key :title
end

class Product < Commentable
  key :sku
end

class Comment
  include MongoMapper::Document
  key :text
  belongs_to :commentable
end

Though you can’t have polymorphism on the belongs_to side with the basic method, with SCI you can:

class Site
  include MongoMapper::Document
  many :pages
end

class Page
  include MongoMapper::Document
  belongs_to :site
end

class HomePage < Page
  key :content
end

class BlogPost < Page
  key :title
  key :body
end

And SCI also allows many-to-many polymorphism, which also can’t be done the basic way. For example, here’s a richer model of books and authors:

class Book
  include MongoMapper::Document
  key :title
  key :author_ids, Array
  many :authors, :in => :author_ids
end

class Novel < Book
  key :genre
end

class Textbook < Book
  key :subject
end

class Author
  include MongoMapper::Document
  key :name
end

class Editor < Author
  key :company_name
end

Single collection inheritance is needed for polymorphism on the belongs_to side and for many-to-many polymorphism because when MongoMapper looks for associated documents, it only fires one query on one collection—therefore all of an object’s children must be located in that one collection. On the other hand, with basic polymorphism the different document types are located in different collections.

Embedded Polymorphism

Embedded documents may also be polymorphic. Both basic polymorphism and SCI polymorphism work fine for all embedded associations. Here’s one example:

class Human
  include MongoMapper::Document
  key :name
  many :contact_methods, :polymorphic => true
end

class ContactMethod
  include MongoMapper::EmbeddedDocument
  key :name
  embedded_in :human
end

class Email < ContactMethod
  key :email
end

# basic polymorphism: it doesn't have to inherit
class Address
  include MongoMapper::EmbeddedDocument
  key :street_address
  key :city
  embedded_in :human
end

MongoMapper stores the class name in a _type key so the objects get loaded as the proper class. For example, a human in the database might look like this when converted to Ruby:

{
  "_id"  => BSON::ObjectId('...'),
  "name" => "Maria"
  "contact_methods" => [
    {
      "_id"   => BSON::ObjectId('...'),
      "_type" => "Email",
      "email" => "mariamusic982452@gmail.com"
    },
    {
      "_id"   => BSON::ObjectId('...'),
      "_type" => "Address",
      "street_address" => "123 Fun St.",
      "city"  => "Nowhere, MI"
    }
  ]
}

Association Extensions

You can extend your core model associations to help you return variations on the associated objects, or encapsulate behavior related to the association.

class Blog
  include MongoMapper::Document

  many :posts do
    def published
      where(:published => true)
    end
  end
end

@blog = Blog.first
@blog.posts.published

If you want reuse an extension between many associations, define a module.

module Publishable
  def published
    where(:published => true)
  end

  def publish!
    set(:published => true)
  end
end

class Blog
  include MongoMapper::Document

  many :posts, :extend => Publishable
  many :comments, :extend => Publishable
end

Sometimes you need access to the proxy owner or target objects in your extension. The following methods are available inside of your extension:

Fork me on GitHub