Mongoid 및 mongodb와의 관계를 통해 has_many를 구현하는 방법은 무엇입니까?


96

Rails 가이드의 수정 된 예제를 사용하여 mongoid를 사용하여 관계형 "has_many : through"연관을 어떻게 모델링합니까?

문제는 mongoid가 ActiveRecord처럼 has_many : through를 지원하지 않는다는 것입니다.

# 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

답변:


151

Mongoid에는 has_many : through 또는 이와 동등한 기능이 없습니다. MongoDB에서는 조인 쿼리를 지원하지 않기 때문에 유용하지 않으므로 다른 컬렉션을 통해 관련 컬렉션을 참조 할 수 있더라도 여전히 여러 쿼리가 필요합니다.

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

일반적으로 RDBMS에 다 대다 관계가있는 경우 양쪽에 '외래'키 배열을 포함하는 필드를 사용하여 MongoDB에서 다르게 모델링합니다. 예를 들면 :

class Physician
  include Mongoid::Document
  has_and_belongs_to_many :patients
end

class Patient
  include Mongoid::Document
  has_and_belongs_to_many :physicians
end

즉, 조인 테이블을 제거하고 '다른 쪽'에 대한 액세스 측면에서 has_many : through와 유사한 효과를 갖습니다. 그러나 귀하의 경우에는 조인 테이블이 연결뿐만 아니라 추가 정보를 전달하는 Appointment 클래스이기 때문에 적절하지 않을 수 있습니다.

이를 모델링하는 방법은 실행해야하는 쿼리에 따라 어느 정도 달라 지지만 약속 모델을 추가하고 다음과 같이 환자 및 의사에 대한 연결을 정의해야하는 것처럼 보입니다.

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의 관계를 사용하면 항상 포함 된 문서 나 관련 문서 중에서 선택해야합니다. 귀하의 모델에서 MeetingNotes가 임베디드 관계에 적합한 후보라고 생각합니다.

class Appointment
  include Mongoid::Document
  embeds_many :meeting_notes
end

class MeetingNote
  include Mongoid::Document
  embedded_in :appointment
end

즉, 약속과 함께 메모를 모두 함께 검색 할 수 있지만 이것이 연관 인 경우 여러 쿼리가 필요합니다. 회의 메모가 매우 많은 경우 단일 문서에 대한 16MB 크기 제한을 염두에 두어야합니다.


7
+1 아주 좋은 답변, 정보를 위해 mongodb 크기 제한이 16MB로 증가했습니다.
rubish

1
호기심으로 (늦게 문의 해 주셔서 죄송합니다) 저도 Mongoid를 처음 접했고 별도의 컬렉션을 사용하여 연결을 저장하는 nn 관계 일 때 데이터를 쿼리하는 방법이 궁금합니다. ActiveRecord로?
이노 스파크

38

이를 확장하기 위해 다음은 레코드 배열 대신 쿼리 프록시를 반환하여 ActiveRecord의 has_many : through와 매우 유사한 방식으로 작동하는 메서드로 확장 된 모델입니다.

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
이것은 내 검색 방법이 페이지 매김을 엉망으로 만든 배열을 반환하는 데 도움이되었습니다.
prasad.surase

1
마법이 아닙니다. @CyrilDD, 당신은 무엇을 언급하고 있습니까? map (& : physician_id)는 map {| appointment |
promise.physician.id

문서가 포함되지 않고 대신 외부 모델을 사용하여 연결된다는 점을 감안할 때이 접근 방식이 16MB 문서 크기 제한에 대한 잠재적 인 좌절감을 줄입니까? (이 멍청한 놈 질문 죄송 경우!)
아틸라 기요 르피

Francis가 설명 .pluck()했듯이 sinstead를 사용 하는 .map것이 훨씬 빠릅니다. 미래의 독자를 위해 답변을 업데이트 할 수 있습니까?
Cyril Duchon-Doris 2015

나는 얻고있다undefined method 'pluck' for #<Array:...>
Wylliam Judd

7

Steven Soroka 솔루션은 정말 훌륭합니다! 나는 답변에 대해 논평 할 평판이 없지만 (그래서 새로운 답변을 추가하고 있습니다 : P) 관계에 맵을 사용하는 것은 비용이 많이 든다고 생각합니다 (특히 has_many 관계에 수백 개의 레코드가있는 경우). 데이터베이스의 데이터는 각 레코드를 만들고 원래 배열을 생성 한 다음 원래 배열을 반복하여 주어진 블록의 값으로 새 배열을 만듭니다.

pluck을 사용하는 것이 더 빠르고 아마도 가장 빠른 옵션 일 것입니다.

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

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> 

250 개의 약속 만 사용하고 있습니다. 약속 문서의 : patient_id 및 : physician_id에 색인을 추가하는 것을 잊지 마십시오!

도움이 되었기를 바랍니다. 읽어 주셔서 감사합니다!


나는 얻고있다undefined method 'pluck' for #<Array:...>
Wylliam Judd

0

has_many : through 관점뿐만 아니라 자체 참조 연관 관점에서이 질문에 답하고 싶습니다.

연락처가있는 CRM이 있다고 가정 해 보겠습니다. 연락처는 다른 연락처와 관계를 갖지만 서로 다른 두 모델 간의 관계를 만드는 대신 동일한 모델의 두 인스턴스간에 관계를 만듭니다. 연락처는 많은 친구를 가질 수 있고 다른 많은 연락처와 친구가 될 수 있으므로 다 대다 관계를 만들어야합니다.

RDBMS 및 ActiveRecord를 사용하는 경우 has_many : through를 사용합니다. 따라서 Friendship과 같은 조인 모델을 만들어야합니다. 이 모델에는 친구를 추가하는 현재 연락처를 나타내는 contact_id와 친구가되는 사용자를 나타내는 friend_id의 두 필드가 있습니다.

하지만 우리는 MongoDB와 Mongoid를 사용하고 있습니다. 위에서 언급했듯이 Mongoid에는 has_many : through 또는 이와 동등한 기능이 없습니다. 조인 쿼리를 지원하지 않기 때문에 MongoDB에서는 유용하지 않습니다. 따라서 MongoDB와 같은 비 RDBMS 데이터베이스에서 다 대다 관계를 모델링하려면 양쪽에 '외래'키 배열이 포함 된 필드를 사용합니다.

class Contact
  include Mongoid::Document
  has_and_belongs_to_many :practices
end

class Practice
  include Mongoid::Document
  has_and_belongs_to_many :contacts
end

문서에 다음과 같이 설명되어 있습니다.

역 문서가 기본 문서와 별도의 컬렉션에 저장되는 다 대다 관계는 Mongoid의 has_and_belongs_to_many 매크로를 사용하여 정의됩니다. 이것은 조인 컬렉션이 필요하지 않다는 점을 제외하면 Active Record와 유사한 동작을 나타냅니다. 외래 키 ID는 관계의 양쪽에 배열로 저장됩니다.

이러한 특성의 관계를 정의 할 때 각 문서는 해당 컬렉션에 저장되고 각 문서에는 배열 형식으로 다른 문서에 대한 "외래 키"참조가 포함됩니다.

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

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

이제 MongoDB의 자체 참조 연결에는 몇 가지 옵션이 있습니다.

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

관련된 연락처와 많은 관행에 속하는 연락처의 차이점은 무엇입니까? 큰 차이! 하나는 두 개체 간의 관계입니다. 기타는 자기 참조입니다.


예제 문서가 같은 것 같습니까?
CyberMew
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.