Bagaimana cara menerapkan has_many: melalui hubungan dengan Mongoid dan mongodb?

96

Menggunakan contoh yang dimodifikasi dari panduan Rails ini , bagaimana cara seseorang memodelkan hubungan "has_many: through" relasional menggunakan mongoid?

Tantangannya adalah bahwa mongoid tidak mendukung has_many: through seperti yang dilakukan 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
sumber

Jawaban:

151

Mongoid tidak memiliki has_many: through atau fitur yang setara. Ini tidak akan begitu berguna dengan MongoDB karena tidak mendukung kueri penggabungan sehingga meskipun Anda dapat mereferensikan koleksi terkait melalui yang lain, itu masih memerlukan banyak kueri.

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

Biasanya jika Anda memiliki hubungan banyak-banyak dalam RDBMS, Anda akan memodelkannya secara berbeda di MongoDB menggunakan bidang yang berisi larik kunci 'asing' di kedua sisi. Sebagai contoh:

class Physician
  include Mongoid::Document
  has_and_belongs_to_many :patients
end

class Patient
  include Mongoid::Document
  has_and_belongs_to_many :physicians
end

Dengan kata lain Anda akan menghilangkan tabel gabungan dan itu akan memiliki efek yang mirip dengan has_many: through dalam hal akses ke 'sisi lain'. Tetapi dalam kasus Anda itu mungkin tidak sesuai karena tabel gabungan Anda adalah kelas janji temu yang membawa beberapa informasi tambahan, bukan hanya asosiasi.

Bagaimana Anda membuat model ini tergantung sampai batas tertentu pada kueri yang perlu Anda jalankan tetapi tampaknya Anda perlu menambahkan model Janji Temu dan mendefinisikan asosiasi ke Pasien dan Dokter seperti ini:

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

Dengan hubungan di MongoDB Anda selalu harus membuat pilihan antara dokumen yang disematkan atau terkait. Dalam model Anda, saya akan menebak bahwa MeetingNotes adalah kandidat yang baik untuk hubungan yang tertanam.

class Appointment
  include Mongoid::Document
  embeds_many :meeting_notes
end

class MeetingNote
  include Mongoid::Document
  embedded_in :appointment
end

Ini berarti Anda bisa mengambil catatan bersama dengan janji temu semuanya, sedangkan Anda akan membutuhkan banyak kueri jika ini adalah asosiasi. Anda hanya perlu mengingat batas ukuran 16MB untuk satu dokumen yang mungkin ikut bermain jika Anda memiliki jumlah catatan rapat yang sangat besar.

Steve
sumber
7
+1 jawaban yang sangat bagus, sekedar info, batas ukuran mongodb telah ditingkatkan menjadi 16 MB.
gosok
1
Karena penasaran (maaf atas penyelidikan yang terlambat), saya juga baru mengenal Mongoid dan saya bertanya-tanya bagaimana Anda akan meminta data ketika itu adalah hubungan nn menggunakan koleksi terpisah untuk menyimpan asosiasi, apakah sama seperti sebelumnya dengan ActiveRecord?
innospark
38

Hanya untuk memperluas ini, berikut adalah model yang diperluas dengan metode yang bertindak sangat mirip dengan has_many: melalui dari ActiveRecord dengan mengembalikan proxy kueri alih-alih array catatan:

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
sumber
2
ini pasti membantu karena metode saya untuk mengambil mengembalikan array yang mengacaukan pagination.
prasad.surase
1
Tidak ada keajaiban. @CyrilDD, apa yang Anda maksud? peta (&: physician_id) adalah singkatan dari peta {| janji temu | janji.physician.id}
Steven Soroka
Saya bertanya-tanya, apakah pendekatan ini mengurangi potensi frustrasi dengan batas ukuran dokumen 16MB, mengingat bahwa dokumen tidak disematkan tetapi dikaitkan menggunakan model luar? (maaf jika ini pertanyaan noob!)
Attila Györffy
Seperti yang dijelaskan Francis, menggunakan .pluck()sinstead dari .mapJAUH lebih cepat. Bisakah Anda memperbarui jawaban Anda untuk pembaca di masa mendatang?
Cyril Duchon-Doris
Saya mendapatkanundefined method 'pluck' for #<Array:...>
Wylliam Judd
7

Solusi Steven Soroka benar-benar hebat! Saya tidak memiliki reputasi untuk mengomentari jawaban (Itulah mengapa saya menambahkan jawaban baru: P) tetapi saya pikir menggunakan peta untuk suatu hubungan itu mahal (terutama jika hubungan has_many Anda memiliki ratusan | ribuan catatan) karena mendapat data dari database, membangun setiap record, menghasilkan array asli dan kemudian melakukan iterasi pada array asli untuk membuat yang baru dengan nilai-nilai dari blok yang diberikan.

Menggunakan petik lebih cepat dan mungkin pilihan tercepat.

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

Berikut beberapa statistik dengan 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> 

Saya hanya menggunakan 250 janji temu. Jangan lupa untuk menambahkan indeks ke: patient_id dan: physician_id di dokumen Perjanjian!

Saya harap ini membantu, Terima kasih telah membaca!

franciscodelgadodev
sumber
Saya mendapatkanundefined method 'pluck' for #<Array:...>
Wylliam Judd
0

Saya ingin menjawab pertanyaan ini dari perspektif asosiasi referensi sendiri, bukan hanya has_many: through perspektif.

Katakanlah kita memiliki CRM dengan kontak. Kontak akan memiliki hubungan dengan kontak lain, tetapi alih-alih membuat hubungan antara dua model yang berbeda, kita akan membuat hubungan antara dua contoh model yang sama. Kontak dapat memiliki banyak teman dan berteman dengan banyak kontak lainnya, jadi kita harus membuat hubungan banyak-ke-banyak.

Jika kita menggunakan RDBMS dan ActiveRecord, kita akan menggunakan has_many: through. Jadi kita perlu membuat model gabungan, seperti Persahabatan. Model ini akan memiliki dua bidang, contact_id yang mewakili kontak saat ini yang menambahkan teman dan friend_id yang mewakili pengguna yang sedang berteman.

Tapi kami menggunakan MongoDB dan Mongoid. Seperti yang dinyatakan di atas, Mongoid tidak memiliki has_many: through atau fitur yang setara. Ini tidak akan begitu berguna dengan MongoDB karena tidak mendukung kueri penggabungan. Oleh karena itu, untuk membuat model hubungan banyak-banyak dalam database non-RDBMS seperti MongoDB, Anda menggunakan kolom yang berisi larik kunci 'asing' di kedua sisinya.

class Contact
  include Mongoid::Document
  has_and_belongs_to_many :practices
end

class Practice
  include Mongoid::Document
  has_and_belongs_to_many :contacts
end

Seperti yang dinyatakan dalam dokumentasi:

Banyak ke banyak hubungan di mana dokumen invers disimpan dalam koleksi terpisah dari dokumen dasar ditentukan menggunakan makro has_and_belongs_to_many Mongoid. Ini menunjukkan perilaku yang mirip dengan Rekaman Aktif dengan pengecualian bahwa tidak diperlukan pengumpulan gabungan, id kunci asing disimpan sebagai array di kedua sisi relasi.

Saat mendefinisikan relasi seperti ini, setiap dokumen disimpan dalam koleksi masing-masing, dan setiap dokumen berisi referensi "kunci asing" satu sama lain dalam bentuk larik.

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

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

Sekarang untuk Asosiasi referensi mandiri di MongoDB, Anda memiliki beberapa opsi.

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

Apa perbedaan antara kontak terkait dan kontak memiliki banyak dan memiliki banyak praktik? Perbedaan besar! Salah satunya adalah hubungan antara dua entitas. Lainnya adalah referensi diri.

Donato
sumber
Contoh dokumennya sepertinya sama?
CyberMew