Associations

Associations are a way of declaring relationships between models, for example a blog Post “has many” Comments, or a Post belongs to an Author. They add a series of methods to your models which allow you to create relationships and retrieve related models along with a few other useful features. Which records are related to which are determined by their foreign keys.

The types of associations currently in DataMapper are:

ActiveRecord Terminology DataMapper 0.9 Terminology
has_many has n
has_one has 1
belongs_to belongs_to, many_to_one
has_and_belongs_to_many has n, :things, :through => Resource
has_many :association, :through => Model has n, :things, :through => :model

Declaring Associations

This is done via declarations inside your model class. The class name of the related model is determined by the symbol you pass in. For illustration, we’ll add an association of each type. Pay attention to the pluralization or the related model’s name.

has n and belongs_to (or One-To-Many)

 1 class Post
 2   include DataMapper::Resource
 3   
 4   has n, :comments
 5 end
 6 
 7 class Comment
 8   include DataMapper::Resource
 9 
10   belongs_to :post
11 end
12 

has n, :through (or One-To-Many-Through)

 1 class Photo
 2   include DataMapper::Resource
 3 
 4   has n, :taggings
 5   has n, :tags, :through => :taggings
 6 end
 7 
 8 class Tag
 9   include DataMapper::Resource
10 
11   has n, :taggings
12   has n, :photos, :through => :taggings
13 end
14 
15 class Tagging
16   include DataMapper::Resource
17 
18   belongs_to :tag
19   belongs_to :photo
20 end
21 

Has, and belongs to, many (Or Many-To-Many)

 1 class Article
 2   include DataMapper::Resource
 3 
 4   has n, :categories, :through => Resource
 5 end
 6 
 7 class Category
 8   include DataMapper::Resource
 9 
10   has n, :articles, :through => Resource
11 end
12 

The use of Resource in place of a class name tells DataMapper to use an anonymous resource to link the two models up.

Adding To Associations

Adding to associations, to add a comment to a post for example, is quite simple.
build or create can be called directly on the association, or an already
existing item can be appended to the association with << and then the item
saved.

 1 # Assume we set up comments and posts as before, as actual models.
 2 
 3 # find a post to add a comment to
 4 @post = Post.get(1)
 5 
 6 # Add the comment
 7 # (also #create can be used - it acts as Comment.create would)
 8 @comment = @post.comments.build(:subject => 'DataMapper ...', ...)
 9 
10 # and save it
11 @comment.save
12 
13 
14 # Or if we already have a comment ...
15 
16 # append it to the comments
17 @post.comments << @comment
18 
19 # and save.
20 @post.save

Customizing Associations

The association declarations make certain assumptions about which classes are being related and the names of foreign keys based on some simple conventions. In some situations you may need to tweak them a little. The association declarations accept additional options to allow you to customize them as you need

1 class Post
2   include DataMapper::Resource
3   belongs_to :author, :class_name => 'User', :child_key => [:post_id]
4 end

Adding Conditions to Associations

If you want to order the association, or supply a scope, you can just pass in the options…

1 class Post
2   include DataMapper::Resource
3   
4   has n, :comments, :order => [:published_on.desc], :rating.gte => 5 
5   # post#comments will now be ordered by published_on, and filtered by rating > 5.
6 end

Finders off Associations

When you call an association off of a model, internally DataMapper creates a Query object which it then executes when you start iterating or call length off of. But if you instead call .all or .first off of the association and provide it the exact same arguments as a regular all and first, it merges the new query with the query from the association and hands you back a requested subset of the association’s query results.

In a way, it acts like a database view in that respect.

1 @post = Post.first
2 @post.categories # returns the full association
3 @post.categories.all(:limit => 10, :order => [:name.asc]) # return the first 10 categories ordered by name
4 @post.categories(:limit => 10, :order => [:name.asc]) # alias for #all, you can pass in the options directly