Wie implementiere ich has_many: durch Beziehungen zu Mongoid und Mongodb?

96

Wie modelliert man anhand dieses modifizierten Beispiels aus den Rails- Handbüchern eine relationale Assoziation "has_many: through" mit Mongoid?

Die Herausforderung besteht darin, dass Mongoid has_many: through nicht unterstützt wie ActiveRecord.

# 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
Mario Zigliotto
quelle

Antworten:

151

Mongoid hat keine has_many: through oder eine gleichwertige Funktion. Es wäre mit MongoDB nicht so nützlich, da es keine Join-Abfragen unterstützt. Selbst wenn Sie eine verwandte Sammlung über eine andere referenzieren könnten, wären dennoch mehrere Abfragen erforderlich.

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

Wenn Sie in einem RDBMS eine Viele-Viele-Beziehung haben, modellieren Sie diese normalerweise in MongoDB anders, indem Sie ein Feld verwenden, das auf beiden Seiten ein Array von 'Fremdschlüsseln' enthält. Beispielsweise:

class Physician
  include Mongoid::Document
  has_and_belongs_to_many :patients
end

class Patient
  include Mongoid::Document
  has_and_belongs_to_many :physicians
end

Mit anderen Worten, Sie würden die Join-Tabelle entfernen und sie hätte einen ähnlichen Effekt wie has_many: durch in Bezug auf den Zugriff auf die 'andere Seite'. In Ihrem Fall ist dies jedoch wahrscheinlich nicht angemessen, da Ihre Join-Tabelle eine Terminklasse ist, die einige zusätzliche Informationen enthält, nicht nur die Zuordnung.

Wie Sie dies modellieren, hängt in gewissem Maße von den Abfragen ab, die Sie ausführen müssen. Es scheint jedoch, als müssten Sie das Terminmodell hinzufügen und Assoziationen zu Patient und Arzt wie folgt definieren:

class Physician
  include Mongoid::Document
  has_many :appointments
end

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

class Patient
  include Mongoid::Document
  has_many :appointments
end

Bei Beziehungen in MongoDB müssen Sie immer zwischen eingebetteten oder zugeordneten Dokumenten wählen. In Ihrem Modell würde ich vermuten, dass MeetingNotes ein guter Kandidat für eine eingebettete Beziehung sind.

class Appointment
  include Mongoid::Document
  embeds_many :meeting_notes
end

class MeetingNote
  include Mongoid::Document
  embedded_in :appointment
end

Dies bedeutet, dass Sie die Notizen zusammen mit einem Termin abrufen können, während Sie mehrere Abfragen benötigen würden, wenn dies eine Zuordnung wäre. Sie müssen nur die Größenbeschränkung von 16 MB für ein einzelnes Dokument berücksichtigen, die möglicherweise ins Spiel kommt, wenn Sie über eine sehr große Anzahl von Besprechungsnotizen verfügen.

Steve
quelle
7
+1 sehr schöne Antwort, nur zur Information, die Größenbeschränkung für Mongodb wurde auf 16 MB erhöht.
Rubish
1
Aus Neugier (Entschuldigung für die späte Anfrage) bin ich auch neu bei Mongoid und habe mich gefragt, wie Sie Daten abfragen würden, wenn es sich um eine nn-Beziehung handelt, bei der eine separate Sammlung zum Speichern der Zuordnung verwendet wird. Ist dies dieselbe wie zuvor? mit ActiveRecord?
Innospark
38

Um dies zu erweitern, sind hier die Modelle, die um Methoden erweitert wurden, die sich sehr ähnlich wie has_many verhalten: von ActiveRecord durch Zurückgeben eines Abfrage-Proxys anstelle eines Arrays von Datensätzen:

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
Steven Soroka
quelle
2
Dies hat sicherlich dazu beigetragen, dass meine Methode zum Abrufen ein Array zurückgegeben hat, das die Paginierung durcheinander gebracht hat.
prasad.surase
1
Keine Magie. @ CyrilDD, worauf beziehen Sie sich? map (&: doctor_id) ist eine Abkürzung für map {| termin | Ernennung.physician.id}
Steven Soroka
Ich frage mich, ob dieser Ansatz die potenzielle Frustration mit der Dokumentgrößenbeschränkung von 16 MB verringert, da die Dokumente nicht eingebettet, sondern mithilfe eines externen Modells verknüpft werden. (Entschuldigung, wenn dies eine Noob-Frage ist!)
Attila Györffy
Wie Francis erklärt, ist die Verwendung von .pluck()sinstead of VIEL .mapschneller. Können Sie Ihre Antwort für zukünftige Leser aktualisieren?
Cyril Duchon-Doris
Ich bekommeundefined method 'pluck' for #<Array:...>
Wylliam Judd
7

Steven Soroka Lösung ist wirklich toll! Ich habe nicht den Ruf, eine Antwort zu kommentieren (deshalb füge ich eine neue Antwort hinzu: P), aber ich denke, die Verwendung einer Karte für eine Beziehung ist teuer (insbesondere, wenn Ihre has_many-Beziehung Hunderte | Tausende von Datensätzen enthält), weil sie erhalten wird Die Daten aus der Datenbank erstellen jeden Datensatz, generieren das ursprüngliche Array und iterieren dann über das ursprüngliche Array, um ein neues mit den Werten aus dem angegebenen Block zu erstellen.

Das Zupfen ist schneller und vielleicht die schnellste Option.

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

Hier einige Statistiken mit 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> 

Ich benutze nur 250 Termine. Vergessen Sie nicht, im Termindokument Indizes zu: patient_id und: doctor_id hinzuzufügen!

Ich hoffe es hilft, Danke fürs Lesen!

franciscodelgadodev
quelle
Ich bekommeundefined method 'pluck' for #<Array:...>
Wylliam Judd
0

Ich möchte diese Frage aus der Perspektive der selbstreferenzierenden Assoziation beantworten, nicht nur aus der Perspektive has_many: through.

Angenommen, wir haben ein CRM mit Kontakten. Kontakte haben Beziehungen zu anderen Kontakten, aber anstatt eine Beziehung zwischen zwei verschiedenen Modellen herzustellen, erstellen wir eine Beziehung zwischen zwei Instanzen desselben Modells. Ein Kontakt kann viele Freunde haben und mit vielen anderen Kontakten befreundet sein, daher müssen wir eine Viele-zu-Viele-Beziehung aufbauen.

Wenn wir ein RDBMS und ActiveRecord verwenden, würden wir has_many: through verwenden. Daher müssten wir ein Join-Modell wie Friendship erstellen. Dieses Modell verfügt über zwei Felder: eine Kontakt-ID, die den aktuellen Kontakt darstellt, der einen Freund hinzufügt, und eine Freund-ID, die den Benutzer darstellt, der befreundet ist.

Aber wir verwenden MongoDB und Mongoid. Wie oben erwähnt, verfügt Mongoid nicht über has_many: through oder eine gleichwertige Funktion. Es wäre mit MongoDB nicht so nützlich, da es keine Join-Abfragen unterstützt. Um eine Viele-Viele-Beziehung in einer Nicht-RDBMS-Datenbank wie MongoDB zu modellieren, verwenden Sie daher ein Feld, das auf beiden Seiten ein Array von 'Fremdschlüsseln' enthält.

class Contact
  include Mongoid::Document
  has_and_belongs_to_many :practices
end

class Practice
  include Mongoid::Document
  has_and_belongs_to_many :contacts
end

Wie in der Dokumentation angegeben:

Viele bis viele Beziehungen, in denen die inversen Dokumente in einer vom Basisdokument getrennten Sammlung gespeichert sind, werden mithilfe des Makros has_and_belongs_to_many von Mongoid definiert. Dies zeigt ein ähnliches Verhalten wie Active Record, mit der Ausnahme, dass keine Join-Sammlung erforderlich ist. Die Fremdschlüssel-IDs werden als Arrays auf beiden Seiten der Beziehung gespeichert.

Bei der Definition einer solchen Beziehung wird jedes Dokument in seiner jeweiligen Sammlung gespeichert, und jedes Dokument enthält einen Fremdschlüsselverweis auf das andere in Form eines Arrays.

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

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

Für eine selbstreferenzierende Zuordnung in MongoDB haben Sie jetzt einige Optionen.

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

Was ist der Unterschied zwischen verwandten Kontakten und Kontakten, die viele haben und zu vielen Praktiken gehören? Großer Unterschied! Eine ist eine Beziehung zwischen zwei Entitäten. Andere ist eine Selbstreferenz.

Donato
quelle
Die Beispieldokumente scheinen gleich zu sein?
CyberMew