Come implementare has_many: attraverso i rapporti con Mongoid e mongodb?


96

Usando questo esempio modificato dalle guide Rails , come si modella un'associazione relazionale "has_many: through" usando mongoid?

La sfida è che mongoid non supporta has_many: through come fa 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

Risposte:


151

Mongoid non ha has_many: through o una funzionalità equivalente. Non sarebbe così utile con MongoDB perché non supporta le query di join, quindi anche se si potesse fare riferimento a una raccolta correlata tramite un'altra, sarebbero comunque necessarie più query.

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

Normalmente, se hai una relazione molti-molti in un RDBMS, la modifichi in modo diverso in MongoDB utilizzando un campo contenente un array di chiavi "esterne" su entrambi i lati. Per esempio:

class Physician
  include Mongoid::Document
  has_and_belongs_to_many :patients
end

class Patient
  include Mongoid::Document
  has_and_belongs_to_many :physicians
end

In altre parole, elimineresti la tabella di join e avrebbe un effetto simile a has_many: through in termini di accesso all '"altro lato". Ma nel tuo caso questo probabilmente non è appropriato perché la tua tabella di join è una classe Appointment che porta alcune informazioni extra, non solo l'associazione.

Il modo in cui lo modifichi dipende in una certa misura dalle query che devi eseguire, ma sembra che dovrai aggiungere il modello Appuntamento e definire associazioni a Paziente e Medico in questo modo:

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

Con le relazioni in MongoDB devi sempre scegliere tra documenti incorporati o associati. Nel tuo modello immagino che MeetingNotes sia un buon candidato per una relazione incorporata.

class Appointment
  include Mongoid::Document
  embeds_many :meeting_notes
end

class MeetingNote
  include Mongoid::Document
  embedded_in :appointment
end

Ciò significa che puoi recuperare le note insieme a un appuntamento tutte insieme, mentre avresti bisogno di più query se questa fosse un'associazione. Devi solo tenere a mente il limite di dimensione di 16 MB per un singolo documento che potrebbe entrare in gioco se hai un numero molto elevato di note di riunione.


7
+1 risposta molto bella, solo per info, il limite di dimensione di mongodb è stato aumentato a 16 MB.
rubish

1
Per curiosità (scusa per la richiesta in ritardo), anch'io sono nuovo di Mongoid e mi chiedevo come interrogheresti i dati quando si tratta di una relazione nn utilizzando una raccolta separata per memorizzare l'associazione, è uguale a prima con ActiveRecord?
innospark

38

Solo per espandere questo, ecco i modelli estesi con metodi che agiscono in modo molto simile a has_many: attraverso da ActiveRecord restituendo un proxy di query invece di un array di record:

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

2
questo sicuramente ha aiutato perché il mio metodo per il recupero restituiva un array che ha incasinato l'impaginazione.
prasad.surase

1
Nessuna magia. @CyrilDD, a cosa ti riferisci? map (&: physician_id) è l'abbreviazione di map {| appuntamento | appuntamento.physician.id}
Steven Soroka

Mi chiedo, questo approccio riduce la potenziale frustrazione con il limite di dimensione del documento di 16 MB, dato che i documenti non sono incorporati ma invece associati utilizzando un modello esterno? (scusate se questa è una domanda noob!)
Attila Györffy

Come spiega Francis, usare .pluck()sin invece di .mapè MOLTO più veloce. Puoi aggiornare la tua risposta per i futuri lettori?
Cyril Duchon-Doris

Sto ricevendoundefined method 'pluck' for #<Array:...>
Wylliam Judd

7

La soluzione di Steven Soroka è davvero fantastica! Non ho la reputazione di commentare una risposta (ecco perché sto aggiungendo una nuova risposta: P) ma penso che usare la mappa per una relazione sia costoso (specialmente se la tua relazione has_many ha centinaia | migliaia di record) perché ottiene i dati dal database, costruire ogni record, genera l'array originale e quindi itera sull'array originale per crearne uno nuovo con i valori dal blocco dato.

Usare spennare è più veloce e forse l'opzione più veloce.

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

Ecco alcune statistiche con 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> 

Sto usando solo 250 appuntamenti. Non dimenticare di aggiungere gli indici a: Patient_id e: physician_id nel documento Appuntamento!

Spero che aiuti, grazie per la lettura!


Sto ottenendoundefined method 'pluck' for #<Array:...>
Wylliam Judd

0

Voglio rispondere a questa domanda dalla prospettiva dell'associazione autoreferenziale, non solo da has_many: attraverso la prospettiva.

Diciamo che abbiamo un CRM con contatti. I contatti avranno relazioni con altri contatti, ma invece di creare una relazione tra due modelli diversi, creeremo una relazione tra due istanze dello stesso modello. Un contatto può avere molti amici e diventare amico di molti altri contatti, quindi dovremo creare una relazione molti-a-molti.

Se stiamo usando un RDBMS e ActiveRecord, useremmo has_many: through. Quindi avremmo bisogno di creare un modello di adesione, come l'amicizia. Questo modello avrebbe due campi, un contact_id che rappresenta il contatto corrente che sta aggiungendo un amico e un friend_id che rappresenta l'utente che sta facendo amicizia.

Ma stiamo usando MongoDB e Mongoid. Come affermato sopra, Mongoid non ha has_many: through o una funzionalità equivalente. Non sarebbe così utile con MongoDB perché non supporta le query di join. Pertanto, per modellare una relazione molti-molti in un database non RDBMS come MongoDB, si utilizza un campo contenente un array di chiavi "esterne" su entrambi i lati.

class Contact
  include Mongoid::Document
  has_and_belongs_to_many :practices
end

class Practice
  include Mongoid::Document
  has_and_belongs_to_many :contacts
end

Come afferma la documentazione:

Le relazioni molti a molti in cui i documenti inversi sono archiviati in una raccolta separata dal documento di base vengono definite utilizzando la macro has_and_belongs_to_many di Mongoid. Ciò presenta un comportamento simile a Active Record con l'eccezione che non è necessaria alcuna raccolta di join, gli ID della chiave esterna vengono archiviati come array su entrambi i lati della relazione.

Quando si definisce una relazione di questa natura, ogni documento viene memorizzato nella rispettiva raccolta e ogni documento contiene un riferimento "chiave esterna" all'altro sotto forma di array.

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

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

Ora per un'associazione autoreferenziale in MongoDB, hai alcune opzioni.

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

Qual è la differenza tra contatti correlati e contatti che hanno molti e appartengono a molte pratiche? Differenza enorme! Uno è una relazione tra due entità. Altro è un autoreferenziale.


I documenti di esempio sembrano essere gli stessi?
CyberMew
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.