[Ruby-on-rails] How to implement has_many :through relationships with Mongoid and mongodb?


Answers

Just to expand on this, here's the models extended with methods that act very similar to the has_many :through from ActiveRecord by returning a query proxy instead of an array of records:

class Physician
  include Mongoid::Document
  has_many :appointments

  def patients
    Patient.in(id: appointments.pluck(:patient_id))
  end
end

class Appointment
  include Mongoid::Document
  belongs_to :physician
  belongs_to :patient
end

class Patient
  include Mongoid::Document
  has_many :appointments

  def physicians
    Physician.in(id: appointments.pluck(:physician_id))
  end
end
Question

Using this modified example from the Rails guides, how does one model a relational "has_many :through" association using mongoid?

The challenge is that mongoid does not support has_many :through as ActiveRecord does.

# doctor checking out patient
class Physician < ActiveRecord::Base
  has_many :appointments
  has_many :patients, :through => :appointments
  has_many :meeting_notes, :through => :appointments
end

# notes taken during the appointment
class MeetingNote < ActiveRecord::Base
  has_many :appointments
  has_many :patients, :through => :appointments
  has_many :physicians, :through => :appointments
end

# the patient
class Patient < ActiveRecord::Base
  has_many :appointments
  has_many :physicians, :through => :appointments
  has_many :meeting_notes, :through => :appointments
end

# the appointment
class Appointment < ActiveRecord::Base
  belongs_to :physician
  belongs_to :patient
  belongs_to :meeting_note
  # has timestamp attribute
end



I want to answer this question from the self-referencing association perspective, not just the has_many :through perspective.

Let's say we have a CRM with contacts. Contacts will have relationships with other contacts, but instead of creating a relationship between two different models, we’ll be creating a relationship between two instances of the same model. A contact can have many friends and be befriended by many other contacts so we’re going to have to create a many-to-many relationship.

If we are using a RDBMS and ActiveRecord, we would use has_many :through. Thus we would need to create a join model, like Friendship. This model would have two fields, a contact_id that represents the current contact who’s adding a friend and a friend_id that represents the user who’s being befriended.

But we are using MongoDB and Mongoid. As stated above, Mongoid doesn't have has_many :through or an equivalent feature. It would not be so useful with MongoDB because it does not support join queries. Therefore, in order to model a many-many relationship in a non-RDBMS database like MongoDB, you use a field containing an array of 'foreign' keys on either side.

class Contact
  include Mongoid::Document
  has_and_belongs_to_many :practices
end

class Practice
  include Mongoid::Document
  has_and_belongs_to_many :contacts
end

As the documentation states:

Many to many relationships where the inverse documents are stored in a separate collection from the base document are defined using Mongoid’s has_and_belongs_to_many macro. This exhibits similar behavior to Active Record with the exception that no join collection is needed, the foreign key ids are stored as arrays on either side of the relation.

When defining a relation of this nature, each document is stored in its respective collection, and each document contains a “foreign key” reference to the other in the form of an array.

# the contact document
{
  "_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
  "practice_ids" : [ ObjectId("4d3ed089fb60ab534684b7f2") ]
}

# the practice document
{
  "_id" : ObjectId("4d3ed089fb60ab534684b7e9"),
  "contact_ids" : [ ObjectId("4d3ed089fb60ab534684b7f2") ]
}

Now for a self-referencing Association in MongoDB, you have a few options.

has_many :related_contacts, :class_name => 'Contact', :inverse_of => :parent_contact
belongs_to :parent_contact, :class_name => 'Contact', :inverse_of => :related_contacts

What is difference between related contacts and contacts having many and belonging to many practices? Huge difference! One is a relationship between two entities. Other is a self-reference.