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