How-to: User has fans

101 views
Skip to first unread message

Christian Fazzini

unread,
Nov 8, 2010, 12:40:38 PM11/8/10
to Mongoid
User has one or many fans.

This is how I have it set up at the moment:

User model:
embeds_many :fans
references_many :fans

Fan model:
embedded_in :user, :inverse_of => :fans
referenced_in :user

In console, I do:
User.first.fans.create!(:user => User.first)

and I get:
Mongoid::Errors::InvalidCollection: Access to the collection for Fan
is not allowed since it is an embedded
document, please access a collection from the root document.

I think the problem is, because the fan model is embedded in the user
model which has a self-reference to the user model as well....

How can I get this to work

Nicholas Young

unread,
Nov 8, 2010, 12:55:50 PM11/8/10
to mon...@googlegroups.com
You'll need to decide how you want to access the fans. If you'll
always be accessing them though the top level collection (the class
User or collection Users) then embedding is fine, and you don't need
the reference.

However, if you'll ever want to access the fans separately, I'd say
just do the reference instead. In either event, you don't need both.

Nicholas

Christian Fazzini

unread,
Nov 8, 2010, 1:04:41 PM11/8/10
to Mongoid
Hi Nicholas, I've decided to embed fans into the User model. The
problem isn't which approach to take, but the fact that if I would
need to embed fans, I would need the user_id in there. Otherwise,
there would be no way to determine who the fan is.

User.first.fans.create!(:user => User.first), should create a fan
record in the user model with the user_id as reference. However, I get
the above mentioned error: "Mongoid::Errors::InvalidCollection: Access
to the collection for Fan is not allowed since it is an embedded
document, please access a collection from the root document."

I am accessing this document from the root already, but why am I
getting this error?

On Nov 9, 1:55 am, Nicholas Young <nicho...@nicholaswyoung.com> wrote:
> You'll need to decide how you want to access the fans. If you'll
> always be accessing them though the top level collection (the class
> User or collection Users) then embedding is fine, and you don't need
> the reference.
>
> However, if you'll ever want to access the fans separately, I'd say
> just do the reference instead. In either event, you don't need both.
>
> Nicholas
>
> On Mon, Nov 8, 2010 at 11:40 AM, Christian Fazzini
>
Message has been deleted

Christian Fazzini

unread,
Nov 8, 2010, 1:15:46 PM11/8/10
to Mongoid
The real question is. If I need to embed fans into the user document.
How should I define the association so that user_id can be referenced
as well?

On Nov 9, 1:55 am, Nicholas Young <nicho...@nicholaswyoung.com> wrote:
> You'll need to decide how you want to access the fans. If you'll
> always be accessing them though the top level collection (the class
> User or collection Users) then embedding is fine, and you don't need
> the reference.
>
> However, if you'll ever want to access the fans separately, I'd say
> just do the reference instead. In either event, you don't need both.
>
> Nicholas
>
> On Mon, Nov 8, 2010 at 11:40 AM, Christian Fazzini
>

Nicholas Young

unread,
Nov 8, 2010, 1:28:29 PM11/8/10
to mon...@googlegroups.com
Hey Christian -

If you use embeds_many :fans and store and access the Fans like so:

u = User.first
u.fans << Fan.new(params)
u.save

When you query for:

u = User.first
u.fans # => this will return all of the User's fans.

No need to explicitly store the user's ID.

I'm still puzzled why you want a reference as well... Are you looking
to query the Fans using different syntax than I outlined above?

Nicholas

Nicholas Young

unread,
Nov 8, 2010, 1:33:41 PM11/8/10
to mon...@googlegroups.com
> User.first.fans.create!(:user => User.first), should create a fan
> record in the user model with the user_id as reference.

Using this syntax, you're assuming that the Fans are stored in a
separate collection (and is referenced in the User model), and thus,
can call create!.
However, if you choose to use embedded documents, you'll need to use
the syntax outlined in my previous email.

Christian Fazzini

unread,
Nov 8, 2010, 1:39:25 PM11/8/10
to Mongoid
u = User.first
u.fans << Fan.new(params)
u.save

Lets assume we are saving with the above code. How do I store the
user_id? What exactly is being passed with params? Can you give me an
example?

Furthermore, in the above example that you depicted, how will I be
able to get the first name of the first fan for User.first if I don't
reference?

For example. I would like to do:

foo = User.first
foo.name (returns 'foo')
foo.fans.first.user.name (should return 'bar', since 'bar' is a fan of
'foo')

On Nov 9, 2:28 am, Nicholas Young <nicho...@nicholaswyoung.com> wrote:
> Hey Christian -
>
> If you use embeds_many :fans and store and access the Fans like so:
>
> u = User.first
> u.fans << Fan.new(params)
> u.save
>
> When you query for:
>
> u = User.first
> u.fans # => this will return all of the User's fans.
>
> No need to explicitly store the user's ID.
>
> I'm still puzzled why you want a reference as well... Are you looking
> to query the Fans using different syntax than I outlined above?
>
> Nicholas
>
> On Mon, Nov 8, 2010 at 12:15 PM, Christian Fazzini
>

Brandon Martin

unread,
Nov 8, 2010, 1:42:24 PM11/8/10
to mon...@googlegroups.com
Christian,

Embedding documents means that they only belong to the parent that they are embedded in. So you could do

fan = User.first.fans.first

to return the first Fan of the first User

Brandon Martin

Christian Fazzini

unread,
Nov 8, 2010, 1:49:34 PM11/8/10
to Mongoid
Brandon, that's what I want to do. I am having trouble setting this
up.

User.first.fans.first should, in theory, work if I set it up the way I
tried on my initial post. But I get an error.
> <http://www.zyphmartin.com>

Nicholas Young

unread,
Nov 8, 2010, 1:50:39 PM11/8/10
to mon...@googlegroups.com
Ok, let's talk about syntax first:

> foo = User.first
> foo.name
> foo.fans.first.user.name

That third line isn't going to look like that. I think your concept of
embedded documents is a bit fuzzy. Take a look here:
http://mongoid.org/docs/associations/ and
http://www.mongodb.org/display/DOCS/Schema+Design

Now, like Brandon said - here's what you'll need to do:

class User
include Mongoid::Document
field :name # add , :type => String, or whatever if you want
embeds_many :fans
end

class Fan
include Mongoid::Document


embedded_in :user, :inverse_of => :fans

field :city # or whatever fields you want to add
end

We can create a

Now, we can query with:

fan = User.first.fans.first # We're getting the first user, and
returning the first item from the embedded array of fan documents

and we can call:

fan.city # This is the address we gave to the fan document

We add new fans like so:

u = User.first
u.fans << Fan.new(:city => "Buffalo")
u.save

Note: this may seem crazy, to those who are used to relational
databases. Because we've embedded the Fan into the user document, we
have to pull the user first, then return that user's fans. MongoDB
does all of the scoping that might be required in MySQL automatically.

Hopefully, this helps.

Nicholas

Christian Fazzini

unread,
Nov 8, 2010, 2:00:44 PM11/8/10
to Mongoid
Nicholas, ok good, you are understanding the situation so far.
However, what if the fan is another user, instead of a city. Like I
mentioned earlier. Bar is a fan of Foo.

With your setup, how will you get the name of Foo's first fan?

On Nov 9, 2:50 am, Nicholas Young <nicho...@nicholaswyoung.com> wrote:
> Ok, let's talk about syntax first:
>
> > foo = User.first
> > foo.name
> > foo.fans.first.user.name
>
> That third line isn't going to look like that. I think your concept of
> embedded documents is a bit fuzzy. Take a look here:http://mongoid.org/docs/associations/andhttp://www.mongodb.org/display/DOCS/Schema+Design

Nicholas Young

unread,
Nov 8, 2010, 2:02:30 PM11/8/10
to mon...@googlegroups.com
You'll create another Fan object, and embed it in the other User's document.

Nicholas

Christian Fazzini

unread,
Nov 8, 2010, 2:03:43 PM11/8/10
to Mongoid
With your setup.

u = User.first
u.fans << Fan.new(:user => User.first(:offset => 1))

will return: NoMethodError: undefined method `klass' for ["_id",
BSON::ObjectId('4cd8321172357e059a00001d')]:Array

Do you see the problem now?

On Nov 9, 3:00 am, Christian Fazzini <christian.fazz...@gmail.com>
wrote:
> Nicholas, ok good, you are understanding the situation so far.
> However, what if the fan is another user, instead of a city. Like I
> mentioned earlier. Bar is a fan of Foo.
>
> With your setup, how will you get the name of Foo's first fan?
>
> On Nov 9, 2:50 am, Nicholas Young <nicho...@nicholaswyoung.com> wrote:
>
> > Ok, let's talk about syntax first:
>
> > > foo = User.first
> > > foo.name
> > > foo.fans.first.user.name
>
> > That third line isn't going to look like that. I think your concept of
> > embedded documents is a bit fuzzy. Take a look here:http://mongoid.org/docs/associations/andhttp://www.mongodb.org/displa...

Christian Fazzini

unread,
Nov 8, 2010, 2:10:18 PM11/8/10
to Mongoid
Do you see my point?

On Nov 9, 3:02 am, Nicholas Young <nicho...@nicholaswyoung.com> wrote:
> You'll create another Fan object, and embed it in the other User's document.
>
> Nicholas
>
> On Mon, Nov 8, 2010 at 1:00 PM, Christian Fazzini
>
> <christian.fazz...@gmail.com> wrote:
> > Nicholas, ok good, you are understanding the situation so far.
> > However, what if the fan is another user, instead of a city. Like I
> > mentioned earlier. Bar is a fan of Foo.
>
> > With your setup, how will you get the name of Foo's first fan?
>
> > On Nov 9, 2:50 am, Nicholas Young <nicho...@nicholaswyoung.com> wrote:
> >> Ok, let's talk about syntax first:
>
> >> > foo = User.first
> >> > foo.name
> >> > foo.fans.first.user.name
>
> >> That third line isn't going to look like that. I think your concept of
> >> embedded documents is a bit fuzzy. Take a look here:http://mongoid.org/docs/associations/andhttp://www.mongodb.org/displa...

Nicholas Young

unread,
Nov 8, 2010, 2:21:02 PM11/8/10
to mon...@googlegroups.com
I do. It really seems like relations may be the better case for you -
if you're uncomfortable with the idea of always having a Fan scoped by
the parent collection, Users.

It really sounds like you need to immerse yourself in the MongoDB way
of thinking, (i.e. read the documentation I sent) then try again to
execute this.

Christian Fazzini

unread,
Nov 8, 2010, 2:30:33 PM11/8/10
to Mongoid
By relations, you mean by reference instead of embedding right?

On Nov 9, 3:21 am, Nicholas Young <nicho...@nicholaswyoung.com> wrote:
> I do. It really seems like relations may be the better case for you -
> if you're uncomfortable with the idea of always having a Fan scoped by
> the parent collection, Users.
>
> It really sounds like you need to immerse yourself in the MongoDB way
> of thinking, (i.e. read the documentation I sent) then try again to
> execute this.
>
> On Mon, Nov 8, 2010 at 1:10 PM, Christian Fazzini
>

Christian Fazzini

unread,
Nov 8, 2010, 2:49:21 PM11/8/10
to Mongoid
Even with referencing it doesn't work! In the app, I need users to be
able to become fans of other users. Either there is something flawed
with mongoid not being able to do this, or I would have to design this
differently

On Nov 9, 3:21 am, Nicholas Young <nicho...@nicholaswyoung.com> wrote:
> I do. It really seems like relations may be the better case for you -
> if you're uncomfortable with the idea of always having a Fan scoped by
> the parent collection, Users.
>
> It really sounds like you need to immerse yourself in the MongoDB way
> of thinking, (i.e. read the documentation I sent) then try again to
> execute this.
>
> On Mon, Nov 8, 2010 at 1:10 PM, Christian Fazzini
>

Brandon Martin

unread,
Nov 8, 2010, 3:04:05 PM11/8/10
to mon...@googlegroups.com
Christian

So do you want a many to many setup?

Users can have many Fans and Fans can have many Users

Because from your initial code it looked like you were only doing

Users have many Fans and Fans belongs to Users

Brandon Martin

Christian Fazzini

unread,
Nov 8, 2010, 3:15:20 PM11/8/10
to Mongoid
Brandon, lets start from the beginning. I just need users to be
able to become fans of other users. Where do I start? Referencing or
embedding, I dont mind either. As long as it works. How should I set
this up?

On Nov 9, 4:04 am, Brandon Martin <bmar...@zyphmartin.com> wrote:
> <http://www.zyphmartin.com>
Message has been deleted

Christian Fazzini

unread,
Nov 8, 2010, 3:44:12 PM11/8/10
to Mongoid
I guess in some ways, I am trying to self-reference the user model. Is
such a thing possible in mongoid?

With each user having an ID for itself and a parentID that points back
to the parent model (user)


On Nov 9, 4:15 am, Christian Fazzini <christian.fazz...@gmail.com>
wrote:

Christian Fazzini

unread,
Nov 9, 2010, 5:49:26 AM11/9/10
to Mongoid
This seems like a bug. The above wont work in mongoid since the
embedded doc (fan) contains a column (user_id) that is self
referencing back to the parent object (user)

https://github.com/mongoid/mongoid/issues/issue/441

On Nov 9, 4:44 am, Christian Fazzini <christian.fazz...@gmail.com>

Brandon Martin

unread,
Nov 9, 2010, 11:05:30 AM11/9/10
to mon...@googlegroups.com
Christian

Maybe I am misunderstanding what you are trying to do but if I am understanding... You have Users and you just want them to be able to become "Fans" of each other or in other words

User can have many fans(other Users)?

If that is the case I would do something like this...

https://gist.github.com/669268

As I said I might be missing what you are trying to do. Hope this is of some help.

Brandon Martin

Paul Elliott

unread,
Nov 9, 2010, 11:26:18 AM11/9/10
to mon...@googlegroups.com
Hey Christian,

You are actually going to want to use the references_and_referenced_in_many macro that will be included in the rc coming out soon. You will need to do it twice, once for each direction of fanning.

--Paul

gilles

unread,
Nov 9, 2010, 2:24:15 PM11/9/10
to Mongoid
This is slightly off topic but can we make it work if the Fan is
really an instance of a User?

would
class User
field :name
references_many :fans, :stored_as => :array, :inverse_of =>:user
end
work?

Or do we have to handle it manually?
class User
field :name
field :fans, :type => Array
end

=> fans being a list of User.id
user.fans << User.create!(params[:fan]).id
or
user.fans << other_user.id

Thanks

--Gilles

On Nov 8, 10:50 am, Nicholas Young <nicho...@nicholaswyoung.com>
wrote:
> Ok, let's talk about syntax first:
>
> > foo = User.first
> > foo.name
> > foo.fans.first.user.name
>
> That third line isn't going to look like that. I think your concept of
> embedded documents is a bit fuzzy. Take a look here:http://mongoid.org/docs/associations/andhttp://www.mongodb.org/display/DOCS/Schema+Design

Christian Fazzini

unread,
Nov 10, 2010, 5:56:25 AM11/10/10
to Mongoid
Hi Paul, roughly when can we expect the RC version to be released? :-)

Paul Elliott

unread,
Nov 10, 2010, 6:47:14 AM11/10/10
to mon...@googlegroups.com
It will hopefully be out in the next week or so. Stay tuned.
Message has been deleted
Message has been deleted

Christian Fazzini

unread,
Nov 14, 2010, 12:38:48 PM11/14/10
to Mongoid
Hi Paul, any news on rc version? Is there a workaround that we can
implement until the rc version is released? Our current project is
pending and waiting for this feature

On Nov 10, 6:56 pm, Christian Fazzini <christian.fazz...@gmail.com>
wrote:

Christian Fazzini

unread,
Nov 14, 2010, 10:18:40 PM11/14/10
to Mongoid
Ooops. Sorry for the re-posts. Didnt know there were being posted...
Deleted them accordingly

On Nov 15, 1:38 am, Christian Fazzini <christian.fazz...@gmail.com>

Christian Fazzini

unread,
Nov 21, 2010, 11:47:27 AM11/21/10
to Mongoid
Any news on the RC update?

On Nov 10, 7:47 pm, Paul Elliott <p...@codingfrontier.com> wrote:
> It will hopefully be out in the next week or so. Stay tuned.
>

Paul Elliott

unread,
Nov 27, 2010, 7:24:26 AM11/27/10
to mon...@googlegroups.com
Hey Christian,

Sorry it's been so quiet. It still isn't quite ready to be rebased into master. I'll share any information when I have some.

--Paul

Christian Fazzini

unread,
Nov 27, 2010, 9:11:27 AM11/27/10
to Mongoid
Thanks Paul. Looking forward the references_and_referenced_in_many
implementation and the RC version altogether. Appreciate the effort.
Keep us posted :-)

Josef Richter

unread,
Nov 28, 2010, 1:11:51 PM11/28/10
to Mongoid
Christian,

I think there are a few solutions, even without that mongoid update.

1. Your User class/document will include an array of ids of fans e.g.
"is fan of":[123,124,125] But if you want to have this relationship
accesible both ways, i.e. also find out who are User's fans, you
actually need another array "has fans":[456,457]. Then when creating
relationship, you need to write to both users - to "is fan of" array
for one user and to "has fans" for the other.

2. You create a "join table", basically a document with properties
"source user" and "target user", meaning source user is a fan of
target user and target user has a fan being the source user. This is
what you would do in a SQL world.

The point is you can implement either of these without the mongoid
update - what the mongoid update is likely to bring you is 1. it will
"automatically" setup those arrays in option 1 and make sure the
relationship is written on both sides at once, 2. it will bring you
some helpers like User.fans.first.name. But you can do both "manually"
now. For example the User.fans.first.name would look something like
User.first(:conditions => {:id => currentuser.fans.first}).name.

It's also good to understand that you are actually modelling a
(social) graph (and basically a twitter clone in this respect, just
with different terminology). If you intend to do things like
recommendations (people who are fans the same people you are fan of,
are also fans of XYZ. in twitter terminology, the people who follow
the same guys you do, also follow these guys, whom you don't yet -
sorry if it sounds confusing at first.) or things like PageRank/
UserRank (i.e. rank not only based on fans count, but also taking into
account these fans' "importance", being their weighted fans count,
etc.) - so if you intend to do this shit in future and you're gonna
have thousands of users, then you might take a look at specialized
graph databases like Neo4j or OrientDB which can do this in reasonable
time.

Also take a look at this "Twitter clone in Redis" - the implementation
in MongoDB would be principially similar. http://code.google.com/p/redis/wiki/TwitterAlikeExample
And a general graph implementation in Redis: https://github.com/dmitriid/blueredis

Does it help?

On Nov 14, 6:38 pm, Christian Fazzini <christian.fazz...@gmail.com>

Robert Riemann

unread,
Nov 28, 2010, 1:25:01 PM11/28/10
to mon...@googlegroups.com

Am Sonntag, 28. November 2010, 19:11:51 schrieb Josef Richter:
> Christian,
>
> I think there are a few solutions, even without that mongoid update.
>
> 1. Your User class/document will include an array of ids of fans e.g.
> "is fan of":[123,124,125] But if you want to have this relationship
> accesible both ways, i.e. also find out who are User's fans, you
> actually need another array "has fans":[456,457]. Then when creating
> relationship, you need to write to both users - to "is fan of" array
> for one user and to "has fans" for the other.
>
- I think, I did something equal (fans have fans),
see discussion on this ML or
https://github.com/saLOUt/rails_tutorial/blob/mongoid/app/models/user.rb

> 2. You create a "join table", basically a document with properties
> "source user" and "target user", meaning source user is a fan of
> target user and target user has a fan being the source user. This is
> what you would do in a SQL world.
- the railstutorial.org implements its follower/following feature this way.
There is also an in-depth explantion.
signature.asc

Christian Fazzini

unread,
Nov 29, 2010, 5:35:39 AM11/29/10
to Mongoid
Josef, Robert thank you for suggesting this alternative! Robert, your
explanation makes a lot of sense. Based on the example at the github
solution that Josef highlighted and for anyone who may come across
this thread in the future:

class User
include Mongoid::Document
include Mongoid::Timestamps

references_many :fans, :stored_as => :array, :class_name =>
'User', :inverse_of => :fan_of
references_many :fan_of, :stored_as => :array, :class_name =>
'User', :inverse_of => :fans

def become_fan_of user
fan_of << user
self.save

user.fans << self
user.save
end

def is_a_fan? user
fan_of_ids.include? user.id
end

def unfan user
fan_of_ids.delete user.id
self.save

user.fan_ids.delete self.id
user.save
end

...
end

On Nov 29, 2:25 am, Robert Riemann <robert.riem...@physik.hu-
berlin.de> wrote:
> Am Sonntag, 28. November 2010, 19:11:51 schrieb Josef Richter:> Christian,
>
> > I think there are a few solutions, even without that mongoid update.
>
> > 1. Your User class/document will include an array of ids of fans e.g.
> > "is fan of":[123,124,125] But if you want to have this relationship
> > accesible both ways, i.e. also find out who are User's fans, you
> > actually need another array "has fans":[456,457]. Then when creating
> > relationship, you need to write to both users - to "is fan of" array
> > for one user and to "has fans" for the other.
>
> - I think, I did something equal (fans have fans),
> see discussion on this ML orhttps://github.com/saLOUt/rails_tutorial/blob/mongoid/app/models/user.rb> 2. You create a "join table", basically a document with properties
> >http://code.google.com/p/redis/wiki/TwitterAlikeExampleAnd a general
> ...
>
> read more »
>
>  signature.asc
> < 1KViewDownload

surana

unread,
Nov 29, 2010, 11:33:50 AM11/29/10
to Mongoid
Hi Christian,

I followed the same model as you put in your post and have been
closely following this thread. I can't still get it to work.

class User
include Mongoid::Document
include Mongoid::Timestamps
field :username, :type => String
field :email, :type => String
field :followers_count, :type => Integer, :default => 0
field :following_count, :type => Integer, :default => 0

references_many :following,
:class_name => 'User',
:stored_as => :array,
:inverse_of => :followers

references_many :followers,
:class_name => 'User',
:stored_as => :array,
:inverse_of => :following

def following? user
following_ids.include? user.id
end

def follow! user
following << user
self.save
user.followers << self
user.save
self.inc(:following_count, 1)
user.inc(:followers_count, 1)
end

def unfollow! user
following_ids.delete user.id
user.follower_ids.delete self.id
user.save!
self.save!
self.inc(:following_count, -1)
user.inc(:followers_count, -1)
end

On executing the follow! method, in the db, I see the count has
increased but the two arrays following_ids and follower_ids are always
empty.

I am using ruby 1.9.2-p0, rails 3 and mongoid 2.0.0.beta.20. New to
this world, so not sure what is wrong and how to debug.


On Nov 29, 5:35 am, Christian Fazzini <christian.fazz...@gmail.com>
wrote:
> > >http://code.google.com/p/redis/wiki/TwitterAlikeExampleAnda general
> ...
>
> read more »

Robert Riemann

unread,
Nov 29, 2010, 1:47:24 PM11/29/10
to mon...@googlegroups.com
Dear surana,

please take a closer look at my example
https://github.com/saLOUt/rails_tutorial/blob/mongoid/app/models/user.rb
.

> following << user
will modify user.followers as well. You don't need your second part.
I tested the above mentioned class in rails console and it works for me.
Maybe you want to take my class as a starting point.

Best regards,
Robert

signature.asc

Christian Fazzini

unread,
Nov 30, 2010, 8:24:21 AM11/30/10
to Mongoid
Hi Surana. Try to copy the exact code I posted. It should work. And
then extend it as you go.

On the side, perhaps validation is not allowing you to save the
object. You could try:

.save(:validate => false)
> ...
>
> read more »

surana

unread,
Nov 30, 2010, 10:02:23 AM11/30/10
to Mongoid
Thanks Christian, it was the validation problem as I have validation
for password. Fixed after I tried with .save(:validate => false)

From your code, in follow!, you don't need to add to both side, just
add one side and save both self and user. It works fine.

thanks,
--

On Nov 30, 8:24 am, Christian Fazzini <christian.fazz...@gmail.com>
> ...
>
> read more »
Reply all
Reply to author
Forward
0 new messages