Some associations still unsupported?

81 views
Skip to first unread message

juliangruber

unread,
Feb 4, 2012, 5:25:30 PM2/4/12
to MongoMapper
Hey,

I'm still new to mongo_wrapper so I hope my question isn't due to my
lack of knowledge.

I want to link two models in a way that I haven't seen examples of yet
with mongo_wrapper:

Model ContentType: name[String].
Model Relation: Each Relation has 2 ContentTypes referenced. rel.from
= [ContentType] rel.to = [ContentType]. Embedding is not an option
since each ContentType can belong to multiple relations.

So basically I have a [ContentType] N:N [Relation], [Relation] N:2
[ContentType] association model.

How can this be done? I tried in the Relation model:
one :content_type, :in => :from
one :from, :class_name => 'ContentType'
and other variations with :typecast eg.

Simply storing the respective _id's as ObjectId is no problem, but
then rel.from is just an id and I have to do the lookups myself via
ContentType.find(rel.from).

The only way I got this working is in the console with
c1 = ContentType.create
c2 = ContentType.create
c1.save!
c2.save!
rel = Relation.create
rel.from = c1
rel.to = c2
rel.save!
rel = Relationship.all.first => right ContentType

But then, rel doesn't save the _ids of the ContentTypes, rather the
_id of the Relation is saved in the content_types' relationship_id
field, which I don't find optimal and adds complexity to simple crud
operations.

Help is greatly appreciated, I hope my problem is clear.

-- Julian Gruber.

John Nunemaker

unread,
Feb 4, 2012, 6:03:22 PM2/4/12
to mongo...@googlegroups.com
So the end goal is relating two content types to each other?


--
You received this message because you are subscribed to the Google
Groups "MongoMapper" group.
For more options, visit this group at
http://groups.google.com/group/mongomapper?hl=en?hl=en

juliangruber

unread,
Feb 5, 2012, 11:41:29 AM2/5/12
to MongoMapper
Yes, but the relation I aim for is not in the docs.

One-to-Many doesn't work because I need 2 belongs_to statements in my
Relationship-Model
Many-to-Many doesn't work because I again have two keys, :from
and :to, that need to reference back.

For clarity, my models atm look like:

-------
class ContentType
include MongoMapper::Document
key :name, String
many :relationships
end
-------
class Relationship
include MongoMapper::Document

key :type, Integer

#one :from, :class_name => 'ContentType', :foreign_key
=> :relationship_ids
#one :to, :class_name => 'ContentType', :foreign_key
=> :relationship_ids
belongs_to :from, :class_name => 'ContentType', :typecast =>
'ObjectId'
belongs_to :to, :class_name => 'ContentType', :typecast =>
'ObjectId'
end
-------

And I want to do:
r = Relationship.create
cfrom = ContentType.create
cto = ContentType.create
r.from = cfrom
r.to = cto
cfrom.relationships

or is this not the right data model with this db and the mongomapper
philosophy?
I could of cause just write custom modelfunctions to update the keys
in the inverse models.

I could save the Relationships embedded in both ContentTypes (I always
want to link 2 ContentTypes) but this also creates management
overhead.

Julian

On Feb 5, 12:03 am, John Nunemaker <nunema...@gmail.com> wrote:
> So the end goal is relating two content types to each other?
>

John Nunemaker

unread,
Feb 6, 2012, 9:32:30 AM2/6/12
to mongo...@googlegroups.com
You want cfrom.relationships to check both the from and the to?

juliangruber

unread,
Feb 6, 2012, 10:33:08 AM2/6/12
to MongoMapper
Ideally cfrom would have an array with all ids...or else yes, check
from and to.

However, I decided to go with embedded documents for relationships for
now, but still I think the original problem is worth solving isn't it?

On Feb 6, 3:32 pm, John Nunemaker <nunema...@gmail.com> wrote:
> You want cfrom.relationships to check both the from and the to?
>

Brian Hempel

unread,
Feb 6, 2012, 11:22:06 AM2/6/12
to mongo...@googlegroups.com
So you want to do join tables in MongoMapper, a la the :through option or has_and_belongs_to_many in ActiveRecord? http://guides.rubyonrails.org/association_basics.html#the-has_many-through-association

I thought about this problem a bunch while working on MongoMapper's association documentation.

There's the supported Many-to-Many, which works find with objects of the same class:

class ContentType
include MongoMapper::Document

key :from_content_type_ids, Array, :typecast => 'ObjectId'
many :from_content_types, :in => :from_content_type_ids

# sorry, no many-to-many inverse yet, our bad!
def to_content_types
ContentType.where(:from_content_type_ids => self.id)
end

def related_content_types
(from_content_types.to_a + to_content_types.to_a).uniq
end
end

cfrom = ContentType.create
cto = ContentType.create

cto.from_content_types << cfrom
cto.related_content_types
cfrom.related_content_types

e.g. in Mongo...

{
_id: ObjectId('abc123'),
from_content_type_ids: [
ObjectId('abc456'),
ObjectId('abc789'),
]
}

{
_id: ObjectId('abc456')
}

{
_id: ObjectId('abc789')
}

You can also roll your own join table as two one-to-many relationships:

class Relationship
include MongoMapper::Document

key :from_content_type_id, ObjectId
key :to_content_type_id, ObjectId
belongs_to :from_content_type, :class_name => 'ContentType'
belongs_to :to_content_type, :class_name => 'ContentType'
end

class ContentType
include MongoMapper::Document

many :from_relationships, :class_name => 'Relationship', :foreign_key => :from_content_type_id
many :to_relationships, :class_name => 'Relationship', :foreign_key => :to_content_type_id

def relationships
from_relationships.to_a + to_relationships.to_a
end

def from_content_types
from_content_type_ids = from_relationships.map { |r| r.from_content_type_id }
ContentType.where(:id.in => from_content_type_ids)
end

def to_content_types
to_content_type_ids = to_relationships.map { |r| r. to_content_type_id }
ContentType.where(:id.in => to_content_types)
end

def related_content_types
(from_content_types.to_a + to_content_types.to_a).uniq
end
end

r = Relationship.create
cfrom = ContentType.create
cto = ContentType.create

r.from_content_type = cfrom
r.to_content_type = cto
r.save
cfrom.relationships
cfrom.related_content_types

Or you can roll your relationship objects as embedded docs. You'd set it up like MongoMapper's standard Many-to-Many BUT instead of storing an array of id's you store an array of embedded documents. It would look like this (note that embedded docs always have their own id):

{
_id: ObjectId('abc123'),
from_content_type_ids: [
{ _id: ObjectId('e321'), from_content_type_id: ObjectId('abc456') },
{ _id: ObjectId('e654'), from_content_type_id: ObjectId('abc789') }
]
}

{
_id: ObjectId('abc456')
}

{
_id: ObjectId('abc789')
}

(Email the list again if you want to go this route and need code help...)

The advantage of embedded docs over MongoMapper's standard Many-to-Many is that you can add attributes to your relationship embedded documents to store more information about the relation, just like you can with ActiveRecord's many :through. For example, you could add a "related_at" timestamp to know when content types were linked up. BUT, if you don't need extra information about the relationship, you don't gain anything with embedded docs over MongoMapper's Many-to-Many.

Development wise, MM needs a Many-to-Many inverse first, then perhaps eager loading, and then maybe we can talk about "join tables" in Mongo. The embedded relationship documents approach is much more Mongo-y than a full out join collection. Embedded relationship document are the same number of queries as the current Many-to-Many implementation. A join collection requires an extra query because Mongo doesn't support joins. With the development pain to code it and maintain it, it might not be worth the gain. Does anyone else need join tables?

Does that answer your question?

Brian

Reply all
Reply to author
Forward
0 new messages