MarkMapper allows you to define the relationship between your models. When you define an association, MarkMapper 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 MarkMapper 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 MarkMapper::Document
many :birds
end
class Bird
include MarkMapper::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.
In a relational database, you might model a many-to-many relationship by creating a “join table.” MarkLogic doesn’t have joins. But because arrays are first class citizens in MarkLogic, 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 MarkMapper::Document
key :title
key :author_ids, Array
many :authors, :in => :author_ids
end
class Author
include MarkMapper::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 MarkMapper.
Use one
and belongs_to
in your models to form a one-to-one association.
class Employee
include MarkMapper::Document
key :name
one :desk
end
class Desk
include MarkMapper::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.
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.
MarkMapper 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 MarkMapper’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 MarkMapper::Document
key :title
many :comments, :as => :commentable
end
class Product
include MarkMapper::Document
key :sku
many :comments, :as => :commentable
end
class Comment
include MarkMapper::Document
key :text
belongs_to :commentable, :polymorphic => true
end
In the database, MarkMapper 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" => MarkMapper::ObjectId('...'),
"text" => "It broke after a month. But it was a good month.",
"commentable_type" => "Product",
"commentable_id" => MarkMapper::ObjectId('...')
}
Polymorphism can also be provided by Single Collection Inheritance. The previous example could be rewritten:
class Commentable
include MarkMapper::Document
many :comments, :as => :commentable
end
class Article < Commentable
key :title
end
class Product < Commentable
key :sku
end
class Comment
include MarkMapper::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 MarkMapper::Document
many :pages
end
class Page
include MarkMapper::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 MarkMapper::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 MarkMapper::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 MarkMapper 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 MarkMapper::Document
key :name
many :contact_methods, :polymorphic => true
end
class ContactMethod
include MarkMapper::EmbeddedDocument
key :name
embedded_in :human
end
class Email < ContactMethod
key :email
end
# basic polymorphism: it doesn't have to inherit
class Address
include MarkMapper::EmbeddedDocument
key :street_address
key :city
embedded_in :human
end
MarkMapper 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" => MarkMapper::ObjectId('...'),
"name" => "Maria"
"contact_methods" => [
{
"_id" => MarkMapper::ObjectId('...'),
"_type" => "Email",
"email" => "mariamusic982452@gmail.com"
},
{
"_id" => MarkMapper::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 MarkMapper::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 MarkMapper::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: