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.
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.
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
endIn 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.
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
endEach 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.
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
endIn this one-to-one relations, an employee can only use one desk, and each desk can only be used by one employee.
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.
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.
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
endIn 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 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
endThough 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
endAnd 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
endSingle 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 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
endMongoMapper 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"
}
]
}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.publishedIf 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
endSometimes you need access to the proxy owner or target objects in your extension. The following methods are available inside of your extension: