Mongoid Multiple Many-to-Many Relations (between two models)


You know the basics of Ruby, MongoDB and Mongoid (and maybe a little bit Rails, but it's not important here, which framework you use).

You use a recent version of the Mongoid gem (~> 2.0.2), which has the has_and_belongs_to_many statement (earlier versions used an alternative syntax for references).

Notice Since version 2.1.x and newer this workaround isn't working around!

You have a lot of fun reading blog posts which mostly contain code snippets.


We have two models: User and Group.

A group can have multiple members and admins.

A user can belong to multiple groups and also can administrate multiple groups.

How these relationships can be managed with Mongoid and its reference logic?


Using mongoid 2.2.x/2.3.x? Jump directly to new solution!

User model

class User
  include Mongoid::Document

  field :name
  field :email

  has_and_belongs_to_many :admin_of,  class_name: "Group"
  has_and_belongs_to_many :member_of, class_name: "Group"


We don't use the group model directly here, instead we define pseudonyms (:admin_of and :member_of), so defining a class_name is mandantory!

The User model is more on the belongs to side, so we must not use the :as statement here.

Group model

class Group
  include Mongoid::Document

  field :name

  has_and_belongs_to_many :admins,  as: :admin_of,  class_name: "User"
  has_and_belongs_to_many :members, as: :member_of, class_name: "User"


We define the pseudonyms :admins and :members, map them to the corresponding references of the User model. The :as statement will build a polymorphic structure internally, but we don't really use this. Of course, don't forget the class_name statement here, too.

Now we have a mixture of many-to-many references, custom relation names and polymorphism.


u1 = "peter foo", email: "")!

u2 = "bjorn bar", email: "")!

u3 = "john baz",  email: "")!

g1 = "pbj common")
g1.admins << u1
g1.members.concat [u1,u2,u3]!

g2 = "pbj music")
g2.admins.concat [u1,u2]
g2.members.concat [u1,u2]!


u = User.first(conditions: {name: "peter foo"})
# => ["pbj masters", "pbj slaves"]

We also can build a admin_of? method to check against a single group.

class User
  # ...

  def admin_of? group
    # also works: group.admins.include?(self)


Furthermore we could write an admin_of method for a single group assignment and rename the reference to admin_of_groups to avoid mind fucks. ;o)


With these models and their relationship levels it's possible to put multiple users in many groups as members and also as admins.

And we haven't to use a complex role system for this!

Conclusion 2 (2011-09-02)

I know, this example is very hacky, and since 2.1.x of mongoid not functional anymore. And furthermore I also prefer another solution to handle User<->Group relations.

So let's take this as an experiment and hack, but nothing to work with.

New solution (2011-10-30)

New example code working with mongoid 2.2.x / 2.3.x:

class User
  include Mongoid::Document

  field :name, type: String

  has_and_belongs_to_many :admin_of, inverse_of: :admins, class_name: 'Group'
  has_and_belongs_to_many :member_of, inverse_of: :members, class_name: 'Group'


class Group
  include Mongoid::Document

  field :name, type: String

  has_and_belongs_to_many :admins,  inverse_of: :admin_of, class_name: 'User'
  has_and_belongs_to_many :members, inverse_of: :member_of, class_name: 'User'


As you can see, the as statement isn't used anymore here (seems that it's only used for polymorphic relations now). Instead the inverse_of in combination with class_name has to be used to have a proper working relationship with better wording.

The origin answer could be found here: "mongoid self to self relationship?" (StackOverflow) - Thanks to SO-User Steve!

Found a mistake?

Have a better idea? A cooler solution?

Then: Please tell me! Write a comment here! Thanks!