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クラスなので、おそらくそれは適切ではありません。

これをモデル化する方法は、実行する必要があるクエリにある程度依存しますが、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のサイズ制限を16 MBに増やしました。
11

1
好奇心から(後半の問い合わせで申し訳ありません)、私もMongoidを初めて使用しました。関連付けを格納するために別のコレクションを使用してnnの関係である場合に、データを照会する方法を考えていました。 ActiveRecord?
innospark 2013

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

1
魔法はありません。@CyrilDD、何を言ってるの?map(&:physician_id)はマップの省略形です{|予定| appointment.physician.id}
Steven Soroka 14

ドキュメントが埋め込まれておらず、外部モデルを使用して関連付けられている場合、このアプローチは、16 MBのドキュメントサイズ制限による潜在的なフラストレーションを軽減しますか?(これがnoobの質問である場合はごめんなさい!)
AttilaGyörffy15年

フランシスが説明するように、の.pluck()代わりにsinを使用する方がはるかに.map高速です。今後の読者のために回答を更新できますか?
Cyril Duchon-Doris、2015

取得中undefined method 'pluck' for #<Array:...>
ウィリアムジャッド

7

スティーブン・ソロカのソリューションは本当に素晴らしいです!回答にコメントする評判はありません(そのため、新しい回答を追加しています: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:...>
ウィリアムジャッド

0

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のような非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は、リレーションの両側に配列として格納されます。

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

# 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つのエンティティ間の関係です。その他は自己参照です。


サンプルドキュメントは同じように見えますか?
Cyber​​Mew、2018年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.