How to implement has_many :through relationships with Mongoid and mongodb? How to implement has_many :through relationships with Mongoid and mongodb? mongodb mongodb

How to implement has_many :through relationships with Mongoid and mongodb?


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 so even if you could reference a related collection via another it would still require multiple queries.

https://github.com/mongoid/mongoid/issues/544

Normally if you have a many-many relationship in a RDBMS you would model that differently in MongoDB using a field containing an array of 'foreign' keys on either side. For example:

class Physician  include Mongoid::Document  has_and_belongs_to_many :patientsendclass Patient  include Mongoid::Document  has_and_belongs_to_many :physiciansend

In other words you would eliminate the join table and it would have a similar effect to has_many :through in terms of access to the 'other side'. But in your case thats probably not appropriate because your join table is an Appointment class which carries some extra information, not just the association.

How you model this depends to some extent on the queries that you need to run but it seems as though you will need to add the Appointment model and define associations to Patient and Physician something like this:

class Physician  include Mongoid::Document  has_many :appointmentsendclass Appointment  include Mongoid::Document  belongs_to :physician  belongs_to :patientendclass Patient  include Mongoid::Document  has_many :appointmentsend

With relationships in MongoDB you always have to make a choice between embedded or associated documents. In your model I would guess that MeetingNotes are a good candidate for an embedded relationship.

class Appointment  include Mongoid::Document  embeds_many :meeting_notesendclass MeetingNote  include Mongoid::Document  embedded_in :appointmentend

This means that you can retrieve the notes together with an appointment all together, whereas you would need multiple queries if this was an association. You just have to bear in mind the 16MB size limit for a single document which might come into play if you have a very large number of meeting notes.


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))  endendclass Appointment  include Mongoid::Document  belongs_to :physician  belongs_to :patientendclass Patient  include Mongoid::Document  has_many :appointments  def physicians    Physician.in(id: appointments.pluck(:physician_id))  endend


Steven Soroka solution is really great! I don't have the reputation to comment an answer(That's why I'm adding a new answer :P) but I think using map for a relationship is expensive(specially if your has_many relationship have hunders|thousands of records) because it gets the data from database, build each record, generates the original array and then iterates over the original array to build a new one with the values from the given block.

Using pluck is faster and maybe the fastest option.

class Physician  include Mongoid::Document  has_many :appointments  def patients    Patient.in(id: appointments.pluck(:patient_id))  endendclass Appointment  include Mongoid::Document  belongs_to :physician  belongs_to :patient endclass Patient  include Mongoid::Document  has_many :appointments   def physicians    Physician.in(id: appointments.pluck(:physician_id))  endend

Here some stats with Benchmark.measure:

> Benchmark.measure { physician.appointments.map(&:patient_id) } => #<Benchmark::Tms:0xb671654 @label="", @real=0.114643818, @cstime=0.0, @cutime=0.0, @stime=0.010000000000000009, @utime=0.06999999999999984, @total=0.07999999999999985> > Benchmark.measure { physician.appointments.pluck(:patient_id) } => #<Benchmark::Tms:0xb6f4054 @label="", @real=0.033517774, @cstime=0.0, @cutime=0.0, @stime=0.0, @utime=0.0, @total=0.0> 

I am using just 250 appointments.Don't forget to add indexes to :patient_id and :physician_id in Appointment document!

I hope it helps,Thanks for reading!