Has_many nasıl uygulanır: Mongoid ve mongodb ile ilişkiler aracılığıyla?


96

Rails kılavuzlarındaki bu değiştirilmiş örneği kullanarak, mongoid kullanarak ilişkisel bir "has_many: through" ilişkilendirmesi nasıl modellenir?

Buradaki zorluk, mongoid'in ActiveRecord'un yaptığı gibi has_many: through'ı desteklememesidir.

# 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

Yanıtlar:


151

Mongoid has_many: through veya eşdeğer bir özelliğe sahip değil. MongoDB ile o kadar kullanışlı olmayacak çünkü birleştirme sorgularını desteklemiyor, bu nedenle ilgili bir koleksiyona başka bir koleksiyonla başvurabilseniz bile, yine de birden fazla sorgu gerektirecektir.

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

Normalde, bir RDBMS'de çok-çok ilişkiniz varsa, her iki tarafta da bir 'yabancı' anahtar dizisi içeren bir alan kullanarak MongoDB'de bunu farklı şekilde modelleyeceksiniz. Örneğin:

class Physician
  include Mongoid::Document
  has_and_belongs_to_many :patients
end

class Patient
  include Mongoid::Document
  has_and_belongs_to_many :physicians
end

Başka bir deyişle, birleştirme tablosunu ortadan kaldırırsınız ve has_many: yoluyla 'diğer tarafa' erişim açısından benzer bir etkiye sahip olur. Ancak sizin durumunuzda bu muhtemelen uygun değildir, çünkü katılma masanız sadece ilişkilendirme değil, bazı ekstra bilgiler de içeren bir Randevu sınıfıdır.

Bunu nasıl modelleyeceğiniz bir dereceye kadar çalıştırmanız gereken sorgulara bağlıdır, ancak Randevu modelini eklemeniz ve Hasta ve Hekime ilişkilendirmeleri tanımlamanız gerekecek gibi görünüyor:

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

MongoDB'deki ilişkilerde her zaman gömülü veya ilişkili belgeler arasında bir seçim yapmanız gerekir. Sizin modelinizde MeetingNotes'un yerleşik bir ilişki için iyi bir aday olduğunu tahmin ediyorum.

class Appointment
  include Mongoid::Document
  embeds_many :meeting_notes
end

class MeetingNote
  include Mongoid::Document
  embedded_in :appointment
end

Bu, notları bir randevuyla birlikte alabileceğiniz anlamına gelirken, bu bir ilişkilendirme olsaydı birden fazla sorguya ihtiyacınız olacaktır. Çok fazla sayıda toplantı notunuz varsa devreye girebilecek tek bir belge için 16 MB boyut sınırını aklınızda bulundurmanız yeterlidir.


7
+1 çok güzel cevap, sadece bilgi için, mongodb boyut sınırı 16 MB'a çıkarıldı.
rubish

1
Merak ettiğim için (geç soruşturma için özür dilerim), ben de Mongoid'te yeniyim ve ilişkiyi depolamak için ayrı bir koleksiyon kullanarak bir nn ilişkisi olduğunda verileri nasıl sorgulayacağınızı merak ediyordum, olduğu gibi mi? ActiveRecord ile
innospark

38

Sadece bunu genişletmek için, has_many'ye çok benzer davranan yöntemlerle genişletilmiş modeller: bir dizi kayıt yerine bir sorgu proxy'si döndürerek ActiveRecord'dan geçerek:

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
bu kesinlikle yardımcı oldu, çünkü geri alma yöntemimin sayfalamayı bozan bir dizi döndürüyordu.
prasad.surase

1
Sihir yok. @CyrilDD, neyi kastediyorsun? harita (&: physician_id) haritanın kısaltmasıdır {| randevu | randevu.physician.id}
Steven Soroka

Acaba, bu yaklaşım, belgelerin gömülü olmadığı, bunun yerine bir dış model kullanılarak ilişkilendirildiği göz önüne alındığında, 16 MB'lık belge boyutu sınırından kaynaklanan olası hayal kırıklığını azaltır mı? (Bu çaylak bir
soruysa

Francis'in açıkladığı gibi, yerine .pluck()sini kullanmak .mapÇOK daha hızlıdır. Cevabınızı gelecekteki okuyucular için güncelleyebilir misiniz?
Cyril Duchon-Doris

Ben alıyorumundefined method 'pluck' for #<Array:...>
Wylliam Judd

7

Steven Soroka çözümü gerçekten harika! Bir cevabı yorumlayacak itibarım yok (Bu yüzden yeni bir cevap ekliyorum: P) ancak bir ilişki için harita kullanmanın pahalı olduğunu düşünüyorum (özellikle has_many ilişkinizde çok fazla | binlerce kayıt varsa) çünkü Veritabanındaki veriler, her bir kaydı oluşturur, orijinal diziyi oluşturur ve ardından verilen bloktan değerlerle yeni bir tane oluşturmak için orijinal dizi üzerinde yineler.

Koparma kullanmak daha hızlıdır ve belki de en hızlı seçenektir.

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

İşte Benchmark.measure ile bazı istatistikler:

> 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> 

Sadece 250 randevu kullanıyorum. Randevu belgesine: hasta_kimliği ve: doktor_kimliği'ne dizin eklemeyi unutmayın!

Umarım yardımcı olur, okuduğunuz için teşekkürler!


Ben alıyorumundefined method 'pluck' for #<Array:...>
Wylliam Judd

0

Bu soruyu sadece has_many açısından değil, kendine referans veren çağrışım perspektifinden yanıtlamak istiyorum: perspektif aracılığıyla.

Diyelim ki kontakları olan bir CRM'miz var. Kişilerin diğer kişilerle ilişkileri olacaktır, ancak iki farklı model arasında bir ilişki oluşturmak yerine, aynı modelin iki örneği arasında bir ilişki oluşturacağız. Bir kişinin birçok arkadaşı olabilir ve diğer birçok kişi tarafından arkadaş olabilir, bu yüzden çoka çok bir ilişki oluşturmamız gerekecek.

Bir RDBMS ve ActiveRecord kullanıyorsak, has_many: through kullanırız. Bu nedenle, Arkadaşlık gibi bir birleştirme modeli oluşturmamız gerekir. Bu model iki alana sahip olacaktır, bir arkadaş ekleyen mevcut kişiyi temsil eden bir kişi_kimliği ve arkadaşlık edilen kullanıcıyı temsil eden bir arkadaş kimliği.

Ancak MongoDB ve Mongoid kullanıyoruz. Yukarıda belirtildiği gibi, Mongoid has_many: through veya eşdeğer bir özelliğe sahip değildir. MongoDB ile o kadar kullanışlı olmaz çünkü birleştirme sorgularını desteklemez. Bu nedenle, MongoDB gibi RDBMS olmayan bir veritabanında çok-çok ilişkisini modellemek için, her iki tarafta bir dizi 'yabancı' anahtar içeren bir alan kullanırsınız.

class Contact
  include Mongoid::Document
  has_and_belongs_to_many :practices
end

class Practice
  include Mongoid::Document
  has_and_belongs_to_many :contacts
end

Belgelerin belirttiği gibi:

Ters belgelerin temel belgeden ayrı bir koleksiyonda depolandığı pek çok ilişkiye, Mongoid'in has_and_belongs_to_many makrosu kullanılarak tanımlanır. Bu, Active Record'a benzer bir davranış sergiler, ancak birleştirme koleksiyonuna gerek yoktur, yabancı anahtar kimlikleri, ilişkinin her iki tarafında diziler olarak depolanır.

Bu nitelikteki bir ilişkiyi tanımlarken, her belge kendi koleksiyonunda saklanır ve her belge, bir dizi biçiminde diğerine bir "yabancı anahtar" referansı içerir.

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

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

Şimdi MongoDB'de kendi kendine referans veren bir Dernek için birkaç seçeneğiniz var.

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

İlgili kişiler ve çok sayıda olan ve birçok uygulamaya ait olan kişiler arasındaki fark nedir? Büyük farklılık! Biri, iki varlık arasındaki bir ilişkidir. Diğeri bir öz referanstır.


Örnek belgeler aynı görünüyor?
CyberMew
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.