Reputation: 25102
I want to store blog and its tags as separate documents.
Blog post should have tag_ids field and tag shouldn't have blog_posts_ids field.
Mongoid provides many to many relations out of the box, but it requires both documents of many to many relation to have _ids field.
class BlogPost
include Mongoid::Document
field :title
references_many :tags, :stored_as => :array, :inverse_of => :blog_posts
end
class Tag
include Mongoid::Document
field :name
# I DON'T WANT TO STORE BLOG_POSTS_IDS IN TAG DOCUMENT
references_many :blog_posts, :stored_as => :array, :inverse_of => :tags
end
Upvotes: 2
Views: 1037
Reputation: 12574
You can get around it with a method on Tag
to fake the other side of the association
class BlogPost
include Mongoid::Document
field :title
references_many :tags, :stored_as => :array, :inverse_of => :blog_posts
end
class Tag
include Mongoid::Document
field :name
def blog_posts
# will match all BlogPost records where the tag_ids array contains self.id
BlogPost.where(:tag_ids => self.id)
end
end
Obviously, this isn't as full-featured as :references_many
, but you can similarly fake other aspects of the many-to-many relation. For example if you want the ability to assign a new blog_post to a tag you can add a simple create_blog_post
method to Tag
.
For many real-world situations, this kind of approach is practical as long as you keep the methods simple and don't get carried away.
Upvotes: 4
Reputation: 728
Map Reduce may be Your answer. Look in to MongoDB map reduce and use the permanent collection output for the tags
class BlogPost
include Mongoid::Document
field :title
field :tags, :type => Array
references_many :tags, :stored_as => :array, :inverse_of => :blog_posts
end
map =<<JS
function(){
if(!this.tags){
return;
}
for(index in this.tags){
emit(this.tags[index],{count:1,posts:[this.post.id]})
}
}
JS
reduce =<<JS
function(key,values){
var tagging = {count:0,posts:new Array()}
values.forEach ( function(val) {
tagging.count++;
tagging.posts.push(val.posts[0])
});
return tagging;
}
JS
BlogPost.collection.map_reduce(map,reduce,{:out => 'tags',:keeptemp => true})
The result collection is always {id,values} where {:id => TAGNAME, :value => {:count => NUMBER_OF_TIMES_TAG_IS_USED,:posts => [ARRAY_OF_BLOG_ARTICLES]}} You can create a formatl Tag class or use:
Mongoid.master.collection("tags")
http://api.mongodb.org/ruby/1.1.2/Mongo/Collection.html#map_reduce-instance_methodt
Upvotes: 2