web-dev-qa-db-ja.com

has_manyを実装する方法:Mongoidおよびmongodbとの関係を通じて?

the Rails guides のこの修正された例を使用して、mongoidを使用してリレーショナル「has_many:through」アソシエーションをどのようにモデル化しますか?

課題は、ActiveRecordがサポートするように、mongoidは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
94
Mario Zigliotto

Mongoidにはhas_many:throughまたは同等の機能はありません。 MongoDBは結合クエリをサポートしていないため、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クラスであるため、おそらく適切ではありません。

これをモデル化する方法は、実行する必要があるクエリにある程度依存しますが、Appointmentモデルを追加し、PatientとPhysicianへの関連付けを次のように定義する必要があるようです。

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のサイズ制限に留意する必要があります。

150
Steve

これを拡張するために、レコードの配列ではなくクエリプロキシを返すことにより、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
36
Steven Soroka

スティーブン・ソロカのソリューションは本当に素晴らしいです!私は答えをコメントする評判はありません(だから私は新しい答えを追加しています:P)データベースからのデータは、各レコードを構築し、元の配列を生成し、元の配列を反復処理して、指定されたブロックの値で新しい配列を構築します。

プラックの使用はより高速で、おそらく最速のオプションです。

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の予定を使用しています。 Appointmentドキュメントの:patient_idと:physician_idにインデックスを追加することを忘れないでください!

読んでくれてありがとう!

6

Has_many:throughパースペクティブだけでなく、自己参照関連のパースペクティブからこの質問に答えたいと思います。

連絡先のあるCRMがあるとします。連絡先には他の連絡先との関係がありますが、2つの異なるモデル間の関係を作成する代わりに、同じモデルの2つのインスタンス間の関係を作成します。連絡先には多くの友人がいる可能性があり、他の多くの連絡先と友達になる可能性があるため、多対多の関係を作成する必要があります。

RDBMSとActiveRecordを使用している場合、has_many:throughを使用します。したがって、Friendshipのような結合モデルを作成する必要があります。このモデルには、友達を追加している現在の連絡先を表すcontact_idと、友達になっているユーザーを表すfriend_idの2つのフィールドがあります。

しかし、MongoDBとMongoidを使用しています。上記のように、Mongoidにはhas_many:throughまたは同等の機能はありません。 MongoDBでは結合クエリをサポートしていないため、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マクロを使用して定義されます。これは、結合コレクションが不要であり、外部キーIDが関係の両側に配列として格納されることを除いて、Active Recordと同様の動作を示します。

この性質の関係を定義する場合、各ドキュメントはそれぞれのコレクションに格納され、各ドキュメントには配列の形式で他への「外部キー」参照が含まれます。

# 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

関連する連絡先と、多くの連絡先と多くのプラクティスに属する連絡先の違いは何ですか?大きな違い! 1つは、2つのエンティティ間の関係です。その他は自己参照です。

0
Donato