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
답변
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 크기 제한을 염두에 두어야합니다.
답변
이를 확장하기 위해 다음은 레코드 배열 대신 쿼리 프록시를 반환하여 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
답변
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에 색인을 추가하는 것을 잊지 마십시오!
도움이 되었기를 바랍니다. 읽어 주셔서 감사합니다!
답변
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
관련된 연락처와 많은 관행에 속하는 연락처의 차이점은 무엇입니까? 큰 차이! 하나는 두 개체 간의 관계입니다. 기타는 자기 참조입니다.