関連付け
項目一覧
参照された関連付け
Mongoid は、ActiveRecord ユーザーに慣れる、 has_one
、 has_many
、 belongs_to
、 has_and_belongs_to_many
の関連付けをサポートします。
が 1 つ
has_one
マイクロを使用して、親が別のコレクションに保存されている子があることを宣言します。 子はデフォルトで任意です。
class Band include Mongoid::Document has_one :studio end
has_one
を使用する場合、子モデルは親との関連付けを宣言するためにbelongs_to
を使用する必要があります。
class Studio include Mongoid::Document belongs_to :band end
上記の定義により、すべての子ドキュメントにはそれぞれの親ドキュメントへの参照が含まれます。
band = Band.create!(studio: Studio.new) # => #<Band _id: 600114fa48966848ad5bd392, > band.studio # => #<Studio _id: 600114fa48966848ad5bd391, band_id: BSON::ObjectId('600114fa48966848ad5bd392')>
子が存在することを要求するには、検証を使用します。
class Band include Mongoid::Document has_one :studio validates_presence_of :studio end
が多い
has_many
関連付けを使用して、親に別のコレクションに保存されている 0 個以上の子があることを宣言します。
class Band include Mongoid::Document has_many :members end
has_one
と同様に、子モデルは親との関連付けを宣言するためにbelongs_to
を使用する必要があります。
class Member include Mongoid::Document belongs_to :band end
また、 has_one
と同様に、子ドキュメントにはそれぞれの親への参照が含まれています。
band = Band.create!(members: [Member.new]) # => #<Band _id: 6001166d4896684910b8d1c5, > band.members # => [#<Member _id: 6001166d4896684910b8d1c6, band_id: BSON::ObjectId('6001166d4896684910b8d1c5')>]
少なくとも 1 つの子が存在することを要求するには、検証を使用します。
class Band include Mongoid::Document has_many :members validates_presence_of :members end
クエリ
any?
関連付けのany?
メソッドを使用して、データベースからドキュメントセット全体を取得することなく、関連付けにドキュメントが含まれているかどうかを効率的に判断します。
band = Band.first band.members.any?
any?
は、 Enumerable#any? API 、ブロックによるフィルタリングが可能になります。
band = Band.first band.members.any? { |member| member.instrument == 'piano' }
... または クラス名で、多態的な 関連付けに役立ちます。
class Drummer < Member end band = Band.first band.members.any?(Drummer)
関連付けがすでにロードされている場合、 any?
はロードされたドキュメントを検査し、データベースをクエリしません。
band = Band.first # Queries the database band.members.any? band.members.to_a # Does not query the database band.members.any?
any?
を呼び出しただけでは関連付けはロードされないことに注意してください( any?
は最初に一致するドキュメントの _id フィールドのみを検索するため)。
exists?
関連付けのexists?
メソッドは、関連付け内に永続化されたドキュメントがあるかどうかを判断します。 any?
メソッドとの違いは、次のとおりです。
exists?
は、関連付けがすでにロードされている場合でも、常にデータベースをクエリします。exists?
は、永続化されていないドキュメントを考慮しません。exists?
では、any?
のようにアプリケーションでフィルタリングすることはできず、引数も取りません。
次の例えでは、 exists?
とany?
の違いを示しています。
band = Band.create! # Member is not persisted. band.members.build band.members.any? # => true band.members.exists? # => false # Persist the member. band.members.map(&:save!) band.members.any? # => true band.members.exists? # => true
に属します
子を別のコレクションに保存されている親に関連付けるには、 belongs_to
マイクロを使用します。 親の_id
(親が関連付けられている場合)は子に保存されます。
デフォルトでは、モデルでbelongs_to
の関連付けが定義されている場合、モデル インスタンスを保存するために 値を指定する必要があります。 親を指定せずに インスタンスを永続的にするには、 optional: true`
オプションを使用します。
class Band include Mongoid::Document has_one :studio end class Studio include Mongoid::Document belongs_to :band, optional: true end studio = Studio.create! # => #<Studio _id: 600118184896684987aa884f, band_id: nil>
belongs_to
関連付けのデフォルト動作を変更し、それぞれの親を必要としないようにするには、 belongs_to_required_by_default
構成オプションをfalse
に設定します。
has_one
とhas_many
の関連付けでは、対応するbelongs_to
の関連付けが子に定義されている必要がありますが、対応するhas_one
またはhas_many
マニュアルなしでbelongs_to
を使用することもできます。 この場合、親から子にはアクセスできませんが、子から親にはアクセスできます。
class Band include Mongoid::Document end class Studio include Mongoid::Document belongs_to :band end
明確にするために、親が関連付けを定義していない場合は、 inverse_of: nil
オプションを追加できます。
class Band include Mongoid::Document end class Studio include Mongoid::Document belongs_to :band, inverse_of: nil end
を持ち、多くに属する
多対多の関連付けを宣言するには、 has_and_belongs_to_many
マイクロを使用します。
class Band include Mongoid::Document has_and_belongs_to_many :tags end class Tag include Mongoid::Document has_and_belongs_to_many :bands end
どちらのモデル インスタンスも、関連付けられているモデルの ID のリストを保存します(存在する場合)。
band = Band.create!(tags: [Tag.create!]) # => #<Band _id: 60011d554896684b8b910a2a, tag_ids: [BSON::ObjectId('60011d554896684b8b910a29')]> band.tags # => [#<Tag _id: 60011d554896684b8b910a29, band_ids: [BSON::ObjectId('60011d554896684b8b910a2a')]>]
inverse_of: nil
オプションを使用して、1 つのドキュメントのみに ID を保存するための一時的なhas_and_belongs_to_many
関連付けを作成できます。
class Band include Mongoid::Document has_and_belongs_to_many :tags, inverse_of: nil end class Tag include Mongoid::Document end band = Band.create!(tags: [Tag.create!]) # => #<Band _id: 60011dbc4896684bbbaa9255, tag_ids: [BSON::ObjectId('60011dbc4896684bbbaa9254')]> band.tags # => [#<Tag _id: 60011dbc4896684bbbaa9254, >]
片側のhas_and_belongs_to_many
関連付けは、自然と、それが定義されているモデルからのみ使用できます。
注意
A と B の 2 つのモデルがあり、ただし、A がhas_and_belongs_to_many
B の場合、B 型のドキュメントを A 型のドキュメントの HATTM 関連付けに追加すると、Mongoid は A 型のドキュメントのupdated_at
フィールドを更新 しません が、タイプ B のドキュメントのupdated_at
フィールドをアップデートします。
参照された関連付けのクエリ
参照された関連付け(および一般にデータまたは条件または複数のコレクションに関係する)にわたる効率的なクエリは、集計パイプラインを使用して実行されます。 集計パイプライン クエリを構築するための Mongoid ヘルパーについては、集計パイプラインセクション で説明されます。
単純なクエリの場合、集計パイプラインの使用を回避し、関連付けを直接クエリできる場合があります。 関連付けを直接クエリする場合、すべての条件はその関連付けの コレクションのみに適用される必要があります(通常、問題中の関連付けと、それに埋め込まれたすべての関連付けを意味します)。
次のモデルを例にしましょう。
class Band include Mongoid::Document has_many :tours has_many :awards field :name, type: String end class Tour include Mongoid::Document belongs_to :band field :year, type: Integer end class Award include Mongoid::Document belongs_to :band field :name, type: String end
次のように、2000 年以降でツアーを担当したすべてのバンドを検索できます。
band_ids = Tour.where(year: {'$gte' => 2000}).pluck(:band_id) bands = Band.find(band_ids)
Tour
の条件は任意に複雑にすることができますが、それらはすべて同じTour
ドキュメント(またはTour
に埋め込まれたドキュメント)に含まれている必要があります。
2000 年以降にツアーを持ったバンドのプライマリを見つけるには以下を行います。
band_ids = Tour.where(year: {'$gte' => 2000}).pluck(:band_id) awards = Award.where(band_id: {'$in' => band_ids})
埋め込み関連付け
MongoDBのdocument modelにより、Mongoid は埋め込み関連付けも提供し、異なるタイプのドキュメントを同じコレクション内で階層的に保存できます。 埋め込み関連付けは、 embeds_one
、 embeds_many
、 embedded_in
マイクロ、および再帰埋め込み用のrecursively_embeds_one
とrecursively_embeds_many
を使用して定義されます。
埋め込み 1
子が親ドキュメントに埋め込まれている 1 対 1 の関連付けは、Mongoid のembeds_one
マニュアルとembedded_in
マイクロを使用して定義されます。
の定義
関連付けの親ドキュメントでは、 embeds_one
コマンドを使用して、 に 1 つの埋め込み子があることを示す必要があり、埋め込まれたドキュメントはembedded_in
を使用する必要があります。 関連付けが正しく機能するためには、関連付けの両方で定義が必要です。
class Band include Mongoid::Document embeds_one :label end class Label include Mongoid::Document field :name, type: String embedded_in :band end
ストレージ
embeds_one
同期を使用して埋め込まれたドキュメントは、親のデータベース コレクション内の親内にハッシュとして保存されます。
{ "_id" : ObjectId("4d3ed089fb60ab534684b7e9"), "label" : { "_id" : ObjectId("4d3ed089fb60ab534684b7e0"), "name" : "Mute", } }
オプションで、 :store_as
オプションを指定すると、埋め込みドキュメントを名前以外の属性に保存するよう Mongoid に指示できます。
class Band include Mongoid::Document embeds_one :label, store_as: "lab" end
埋め込みが多い
子が親ドキュメントに埋め込まれている 1 対多の関係は、Mongoid のembeds_many
とembedded_in
マイクロを使用して定義されます。
の定義
関連付けの親ドキュメントには、多数の埋め込み子があることを示すためにembeds_many
マイクロが使用されます。埋め込まれたドキュメントはembedded_in
を使用します。 関連付けが正しく機能するためには、関連付けの両方に定義が必要です。
class Band include Mongoid::Document embeds_many :albums end class Album include Mongoid::Document field :name, type: String embedded_in :band end
ストレージ
embeds_many
同期を使用して埋め込まれたドキュメントは、親のデータベース コレクション内の親内にハッシュの配列として保存されます。
{ "_id" : ObjectId("4d3ed089fb60ab534684b7e9"), "albums" : [ { "_id" : ObjectId("4d3ed089fb60ab534684b7e0"), "name" : "Violator", } ] }
オプションで、 :store_as
オプションを指定すると、埋め込みドキュメントを名前以外の属性に保存するよう Mongoid に指示できます。
class Band include Mongoid::Document embeds_many :albums, store_as: "albs" end
再帰埋め込み
ドキュメントはrecursively_embeds_one
またはrecursively_embeds_many
を使用して自分自身を再帰的に埋め込むことができ、parent_
メソッドとchild_
メソッドを介して親と子へのアクセスを提供します。
class Tag include Mongoid::Document field :name, type: String recursively_embeds_many end root = Tag.new(name: "programming") child_one = root.child_tags.build child_two = root.child_tags.build root.child_tags # [ child_one, child_two ] child_one.parent_tag # [ root ] child_two.parent_tag # [ root ] class Node include Mongoid::Document recursively_embeds_one end root = Node.new child = Node.new root.child_node = child root.child_node # child child.parent_node # root
参照と埋め込み
参照と埋め込みの完全な説明はこのチュートリアルの範囲外ですが、どちらか一方を選択するための高レベルの考慮事項がいくつかあります。
関連付けが埋め込まれると、親ドキュメントと子ドキュメントの両方が同じコレクションに保存されます。 これにより、両方が使用される場合や必要な場合に、効率的な永続化と取得が可能になります。 たとえば、ウェブ サイトのナビゲーション バーに、ドキュメント自体に保存されているユーザーの属性が表示される場合は、埋め込み関連付けを使用する方が適していることが多いです。
埋め込み関連付けを使用すると、集計パイプラインなどの MongoDB ツールを使用して、これらのドキュメントを強力な方法でクエリできます。
埋め込みドキュメントは親の最上位ドキュメントの一部として保存されるため、埋め込みドキュメントだけで永続化することはできず、また埋め込みドキュメントを直接検索することもできません。 ただし、MongoDB プロジェクション操作の支援を利用すると、埋め込みドキュメントは引き続き効率的にクエリおよび取得できます。
class Band include Mongoid::Document field :started_on, type: Date embeds_one :label end class Label include Mongoid::Document field :name, type: String embedded_in :band end # Retrieve labels for bands started in the last year. # # Sends a find query like this: # {"find"=>"bands", # "filter"=>{"started_on"=>{"$gt"=>2018-07-01 00:00:00 UTC}}, # "projection"=>{"_id"=>1, "label"=>1}} Band.where(started_on: {'$gt' => Time.now - 1.year}).only(:label).map(&:label).compact.uniq
参照された関連付けでの古い値の設定
参照先の関連付けに古い値を設定すると、 nil
値がデータベースに永続化されることがあります。 次のケースで考えてみましょう。
class Post include Mongoid::Document has_one :comment, inverse_of: :post end class Comment include Mongoid::Document belongs_to :post, inverse_of: :comment, optional: true end post.comment = comment1 post.reload
この時点で、 post.comment
はcomment1
に設定されていますが、再読み込みが発生したため、 post.comment
はcomment1
と同じオブジェクトを参照しなくなりました。 つまり、1 つのオブジェクトを更新しても、他のオブジェクトは暗黙的に更新されることはありません。 これは、次の操作にとって重要です。
post.comment = comment2 post.reload
現在、 post.comment
はcomment2
に設定され、古いコメントのpost_id
はnil
に設定されています。 ただし、 post.comment
に割り当てられた値はcomment1
と同じオブジェクトを参照していなかったため、 post.comment
の古い値はnil
post_id
になるように更新されていましたが、 comment1
は引き続き を含みます。 post_id
セット
post.comment = comment1 post.reload
最後に、この最後の割り当てはcomment1
にpost_id
を設定しようとします。この時点でnil
である必要がありますが、古いpost_id
に設定されています。 この操作中、 post_id
はcomment2
からクリアされ、新しいpost_id
がcomment1
に設定されます。 ただし、 post_id
はすでにcomment1
に設定されていたため、何も保持されず、両方のコメントにnil
post_id
が含まれるようになります。 この時点で、 を実行するとpost.comment
nil
が返されます。
埋め込み関連付けのクエリ
最上位ドキュメントをクエリする場合、ドット表記を使用して埋め込み関連付け内のドキュメントに条件を指定できます。 次のモデルを例にしましょう。
class Band include Mongoid::Document embeds_many :tours embeds_many :awards field :name, type: String end class Tour include Mongoid::Document embedded_in :band field :year, type: Integer end class Award include Mongoid::Document embedded_in :band field :name, type: String end
ツアー属性に基づいてバンドを検索するには、次のようにドット表記を使用します。
# Get all bands that have toured since 2000 Band.where('tours.year' => {'$gte' => 2000})
最上位のドキュメントを取得せずに、埋め込み関連付けのドキュメントのみを取得するには、 pluck
プロジェクション メソッドを使用します。
# Get awards for bands that have toured since 2000 Band.where('tours.year' => {'$gte' => 2000}).pluck(:awards)
ロードされた関連付けのクエリ
Mongoid クエリ メソッドは、アプリケーションにすでにロードされているドキュメントの埋め込み関連付けで使用できます。 このメカニズムは「埋め込み一致」と呼ばれ、Mongoid に完全に実装されています。クエリはサーバーに送信されません。
次の演算子がサポートされています。
$regex (
$options
フィールドは$regex
引数が string の場合にのみサポートされます)
たとえば、先ほど指定されたモデル定義を使用して、ロードされたバンド上のツアーをクエリできます。
band = Band.where(name: 'Astral Projection').first tours = band.tours.where(year: {'$gte' => 2000})
埋め込み一致とサーバーの動作
Mongoid の埋め込みマッチングは、最新の MongoDB サーバー バージョンでネイティブ クエリと同じ機能とセマンティクスをサポートすることを目的としています。 次の既知の制限に注意してください。
埋め込み一致は、テキスト検索、地理空間クエリ演算子、JavaScript コードを実行する演算子( $where )、および$exprや$jsonSchema などの他のサーバー機能によって実装されている演算子には実装されていません。
Mongoid DSL は、
$gte
と$lte
条件を持つハッシュに、Range
引数を展開します。 場合によっては これにより誤ったクエリが作成されます。このような場合、埋め込みマッチャーはInvalidQuery
の例外を発生させます。 影響を受けることが確認されている演算子は、$elemMatch
、$eq
、$gt
、$gte
、$lt
、$lte
、$ne
です。$regex
で埋め込み一致を実行する場合、 現在は 正規表現オブジェクトをパターンとして指定し、オプションも提供します。MongoDB Server 4.0およびそれ以前のサーバーは、
$type
の引数を厳密に検証しません(たとえば、 0のような無効な引数を許可する)。 これは、クライアント側でより厳密に検証されます。
フィールドの省略_id
デフォルトでは、Mongoid は各埋め込みドキュメントに_id
フィールドを追加します。 これにより、埋め込みドキュメントに対して を簡単に参照したり、操作を実行したりできるようになります。
これらの_id
フィールドは、ストレージ容量を節約するために省略することができます。 そのためには、子ドキュメント内の _id フィールドの定義を上書きし、デフォルト値を削除します。
class Order include Mongoid::Document embeds_many :line_items end class LineItem include Mongoid::Document embedded_in :order field :_id, type: Object end
Mongoid の現在のバージョンでは フィールド定義は必須ですが、デフォルト値が指定されていない場合、データベースに値は保存されません。 Mongoid の将来のバージョンでは、以前に定義されたフィールドを削除できる可能性があります。
注意
_id
フィールドを削除する場合、クエリ、更新、削除中に埋め込みドキュメントはコンテンツ属性値によって識別される必要があることを意味します。
削除
Mongoid では、 embeds_many
の関連付けから子を削除するための 3 つの方法clear
、 destroy_all
、 delete_all
が提供されています。
clear
clear
メソッドは$unset 演算子を使用して、ホスト ドキュメントから関連付け全体を削除します。 削除されるドキュメントに対して破棄コールバックは実行されず、この点ではdelete_all
のように動作します。
band = Band.find(...) band.tours.clear
保存されていないホスト ドキュメントの 関連付けでclear
が呼び出された場合でも、ホスト ドキュメントの_id
に基づいてデータベースから関連付けを削除しようとします。
band = Band.find(...) band.tours << Tour.new(...) unsaved_band = Band.new(id: band.id, tours: [Tour.new]) # Removes all tours from the persisted band due to _id match. unsaved_band.tours.clear band.tours # => []
delete_all
delete_all
メソッドは、 $pullAll 演算子を使用して 関連付けにあるドキュメントを削除します。 clear
と違い、 delete_all
は次の操作を行います。
関連付けがまだロードされていない場合は、関連付けをロードします。
アプリケーション内に存在するドキュメントのみを削除します。
delete_all
は、削除されるドキュメントに対して破棄コールバックを実行しません。
例:
band = Band.find(...) band.tours.delete_all
destroy_all
delete_all
メソッドは、破棄 コールバックの実行中に$pullAll 演算子を使用して 関連付けられているドキュメントを削除します。 delete_all
と同様に、 destroy_all
は関連付けがまだロードされていない場合は関連付け全体をロードし、アプリケーション内に存在するドキュメントのみを削除します。
band = Band.find(...) band.tours.destroy_all
ハッシュ割り当て
埋め込み関連付けを使用すると、ユーザーはドキュメントではなくHash
を関連付けに割り当てることができます。 割り当て時に、このハッシュは割り当てられている関連付けのクラスのドキュメントに強制されます。 次の例で考えてみましょう。
class Band include Mongoid::Document embeds_many :albums end class Album include Mongoid::Document field :name, type: String embedded_in :band end band = Band.create! band.albums = [ { name: "Narrow Stairs" }, { name: "Transatlanticism" } ] p band.albums # => [ #<Album _id: 633c71e93282a4357bb608e5, name: "Narrow Stairs">, #<Album _id: 633c71e93282a4357bb608e6, name: "Transatlanticism"> ]
これは、 embeds_one
、 embeds_many
、 embedded_in
の関連付けで機能します。 参照された関連付けにはハッシュを割り当てることはできないことに注意してください。
一般的な動作
拡張機能
すべての関連付けには拡張機能を含めることができます。これにより、関連付けにアプリケーション固有の機能を追加できるようになります。 これらは関連付け定義に ブロックを提供することによって定義されます。
class Person include Mongoid::Document embeds_many :addresses do def find_by_country(country) where(country: country).first end def chinese _target.select { |address| address.country == "China" } end end end person.addresses.find_by_country("Mongolia") # returns address person.addresses.chinese # returns [ address ]
カスタム関連付け名
関連付けには任意の名前を付けることができますが、クラスが Mongoid によって名前から推論され、
class Car include Mongoid::Document embeds_one :engine, class_name: "Motor", inverse_of: :machine end class Motor include Mongoid::Document embedded_in :machine, class_name: "Car", inverse_of: :engine end
カスタムプライマリキーと外部キー
関連を検索するときに使用されるフィールドを明示的に指定できます。 デフォルトでは、「親」関連付けではid
を使用し、「子」関連付けでは#{association_name}_id
を使用します。たとえば、 has_many/belongs_to では次のようになります。
class Company include Mongoid::Document has_many :emails end class Email include Mongoid::Document belongs_to :company end company = Company.find(id) # looks up emails where emails.company_id == company.id company.emails
「親」関連付けのフィールド名を変更するには別のprimary_key
を指定し、「子」関連付けのフィールド名を変更するにはforeign_key
を指定します。
class Company include Mongoid::Document field :c, type: String has_many :emails, foreign_key: 'c_ref', primary_key: 'c' end class Email include Mongoid::Document # This definition of c_ref is automatically generated by Mongoid: # field :c_ref, type: Object # But the type can also be specified: field :c_ref, type: String belongs_to :company, foreign_key: 'c_ref', primary_key: 'c' end company = Company.find(id) # looks up emails where emails.c_ref == company.c company.emails
has_and_belongs_to_many 関連付けでは、関連付けの両方の側にデータが保存されるため、関連付けが定義されるときに構成可能なフィールドは 4 つあります。
:primary_key
は、リモート モデルが検索する値を含むリモート モデルの フィールドです。:foreign_key
は、:primary_key
値を保存するローカルモデルの フィールドです。:inverse_primary_key
は、リモート モデルがローカル モデル ドキュメントを検索するために使用するローカル モデルの フィールドです。:inverse_foreign_key
は、:inverse_primary_key
に値を保存しているリモート モデルの フィールドです。
例を使うと、より明確になることがあります。
class Company include Mongoid::Document field :c_id, type: Integer field :e_ids, type: Array has_and_belongs_to_many :employees, primary_key: :e_id, foreign_key: :e_ids, inverse_primary_key: :c_id, inverse_foreign_key: :c_ids end class Employee include Mongoid::Document field :e_id, type: Integer field :c_ids, type: Array has_and_belongs_to_many :companies, primary_key: :c_id, foreign_key: :c_ids, inverse_primary_key: :e_id, inverse_foreign_key: :e_ids end company = Company.create!(c_id: 123) # => #<Company _id: 5c565ece026d7c461d8a9d4e, c_id: 123, e_ids: nil> employee = Employee.create!(e_id: 456) # => #<Employee _id: 5c565ee8026d7c461d8a9d4f, e_id: 456, c_ids: nil> company.employees << employee company # => #<Company _id: 5c565ece026d7c461d8a9d4e, c_id: 123, e_ids: [456]> employee # => #<Employee _id: 5c5883ce026d7c4b9e244c0c, e_id: 456, c_ids: [123]>
デフォルトの#{association_name}_id
フィールドと同様に、Mongoid はカスタム外部キーc_ref
のフィールドをモデルに自動的に追加することに注意してください。 ただし、Mongoid は フィールドで許可されるデータの型を認識していないため、フィールドは オブジェクト タイプで作成されます。 適切なタイプの フィールドを明示的に定義することをお勧めします。
カスタムスコープ
:scope
パラメータを使用して、関連付けに特定のスコープを設定できます。 スコープは、関連付けの一部と見なされるオブジェクトを制限する追加のフィルターです。スコープ付きの関連付けでは、スコープ条件を満たすドキュメントのみが返されます。 スコープは次のいずれかになります。
引数がゼロの
Proc
、または関連するモデル上の名前付きスコープを参照する
Symbol
。
class Trainer has_many :pets, scope: -> { where(species: 'dog') } has_many :toys, scope: :rubber end class Pet belongs_to :trainer end class Toy scope :rubber, where(material: 'rubber') belongs_to :trainer end
注意
関連付けのスコープを満たさないドキュメントをその関連付けに追加することができます。 この場合、そのようなドキュメントはメモリに関連付けられているように表示され、 データベースに保存されますが、将来的に関連付けが照会された時には存在しません。 例:
trainer = Trainer.create! dog = Pet.create!(trainer: trainer, species: 'dog') cat = Pet.create!(trainer: trainer, species: 'cat') trainer.pets #=> [dog, cat] trainer.reload.pets #=> [dog]
注意
Mongoid のスコープ指定された関連付けの構文は、ActiveRecord の構文と異なります。 Mongoid は他の関連付けオプションとの整合性のために:scope
キーワード引数を使用しますが、ActiveRecord ではスコープは位置引数です。
検証
Mongoid はデフォルトで、 validates_associated
経由でメモリにロードされる関連付けの子を検証することに注意してください。 これが適用される関連付けは次のとおりです。
embeds_many
embeds_one
has_many
has_one
has_and_belongs_to_many
この動作が望ましくない場合は、関連付けを定義するときにオフにできます。
class Person include Mongoid::Document embeds_many :addresses, validate: false has_many :posts, validate: false end
多形
1 と 1 および 1 対多の関連付けは多形データをサポートしており、1 つの関連付けに異なるクラスのオブジェクトが含まれる可能性があります。 たとえば、次のように、部門とチームにマネージャーがいる組織をモデル化できます。
class Department include Mongoid::Document has_one :manager, as: :unit end class Team include Mongoid::Document has_one :manager, as: :unit end class Manager include Mongoid::Document belongs_to :unit, polymorphic: true end dept = Department.create! team = Team.create! alice = Manager.create!(unit: dept) alice.unit == dept # => true dept.manager == alice # => true
別の例えとして、製品とバンドルの価格履歴を追跡する場合を考えてみましょう。 これは、埋め込まれた 1 と多態の関連付けによって実行されます。
class Product include Mongoid::Document field :name, type: String has_and_belongs_to_many :bundles embeds_many :prices, as: :item end class Bundle include Mongoid::Document field :name, type: String has_and_belongs_to_many :products embeds_many :prices, as: :item end class Price include Mongoid::Document embedded_in :item, polymorphic: true end pants = Product.create!(name: 'Pants', prices: [Price.new, Price.new]) costume = Bundle.create!(name: 'Costume', products: [pants], prices: [Price.new, Price.new])
多形の関連付けを定義するには、子関連付けでpolymorphic: true
オプションを指定し、親関連付けにas: :association_name
オプションを追加します。
Mongoid は現在、子から親への一方向のみをサポートしていることに注意してください。 たとえば、多形データを使用して、バンドルに他のバンドルまたは製品が含まれることを指定することはできません。
class Bundle include Mongoid::Document # Does not work: has_many :items, polymorphic: true end
has_and_belongs_to_many
関連付けは多形データをサポートしていません。
段階的なコールバック
親で永続化操作を呼び出すときに埋め込みドキュメントのコールバックを起動するには、関連付けに カスケード コールバック オプションを指定する必要があります。
class Band include Mongoid::Document embeds_many :albums, cascade_callbacks: true embeds_one :label, cascade_callbacks: true end band.save # Fires all save callbacks on the band, albums, and label.
依存関係の動作
参照された関連付けに依存関係オプションを指定すると、関連付けのいずれかが削除された場合、または削除が試みられた場合の状況を処理する方法を Mongoid に指示できます。 オプションは次のとおりです。
:delete_all
: モデル コールバックを実行せずに子ドキュメントを削除します。:destroy
: 子ドキュメントを破棄し、すべてのモデル コールバックを実行します。:nullify
: 子ドキュメントの外部キー フィールドを nil に設定します。 通常、親経由でのみ参照される場合、子が 孤立する ことがあります。:restrict_with_exception
: 子が空でない場合は、raise
エラーが発生します。:restrict_with_error
: 子が空でない場合は、操作をキャンセルし、false を返します。
:dependent
オプションが指定されていない場合、親ドキュメントを削除すると、子ドキュメントは変更されません(つまり、子ドキュメントは 外部キー フィールドを介して、削除された親ドキュメントを引き続き参照します)。 通常、親経由でのみ参照される場合、子が 孤立する ことがあります。
class Band include Mongoid::Document has_many :albums, dependent: :delete_all belongs_to :label, dependent: :nullify end class Album include Mongoid::Document belongs_to :band end class Label include Mongoid::Document has_many :bands, dependent: :restrict_with_exception end label = Label.first label.bands.push(Band.first) label.delete # Raises an error since bands is not empty. Band.first.destroy # Will delete all associated albums.
自動保存
Mongoid と ActiveRecord の 1 つの重要な違いは、パフォーマンス上の理由により、親が保存されている場合に、参照済み(埋め込み以外の)関連付けの関連ドキュメントを自動的に保存しないということです。
自動保存が使用されていない場合は、関連付けを介して存在しないドキュメントへの接続のみを作成することが可能です。
class Band include Mongoid::Document has_many :albums end class Album include Mongoid::Document belongs_to :band end band = Band.new album = Album.create!(band: band) # The band is not persisted at this point. album.reload album.band_id # => BSON::ObjectId('6257699753aefe153121a3d5') # Band does not exist. album.band # => nil
親が保存されているときに参照された関連付けが自動的に保存されるようにするには、関連付けに:autosave
オプションを追加します。
class Band include Mongoid::Document has_many :albums end class Album include Mongoid::Document belongs_to :band, autosave: true end band = Band.new album = Album.create!(band: band) # The band is persisted at this point. album.reload album.band_id # => BSON::ObjectId('62576b4b53aefe178b65b8e3') album.band # => #<Band _id: 62576b4b53aefe178b65b8e3, >
accepts_nested_attributes_for
を使用すると、自動保存機能が関連付けに自動的に追加されるため、アプリケーションはフォーム送信の処理時にどの関連付けが変更されたかを追跡する必要がありません。
埋め込み関連付けは親ドキュメントの一部として保存されるため、常に自動保存されます。
関連付けに対する一部の操作では、自動保存が有効になっているかどうかに関係なく、操作の一部として親ドキュメントと子ドキュメントが常に保存されます。 これらの操作の非網羅的なリストは次のとおりです。
関連付けへの割り当て
# Saves the band and the album. band.albums = [Album.new] push
,<<
:band.albums << Album.new band.albums.push(Album.new)
存在述語
すべての関連付けには、関連付けが空白かどうかを確認するためのname?
とhas_name?
の形式でそれらに対する存在述語があります。
class Band include Mongoid::Document embeds_one :label embeds_many :albums end band.label? band.has_label? band.albums? band.has_albums?
自動構築
1 対 1 の関連付け( embeds_one
、 has_one
)には、関連付けがアクセスされ、それがnil
の場合に Mongoid に新しいドキュメントをインスタンス化するように指示する自動構築オプションがあります。
class Band include Mongoid::Document embeds_one :label, autobuild: true has_one :producer, autobuild: true end band = Band.new band.label # Returns a new empty label. band.producer # Returns a new empty producer.
サこと
すべてのbelongs_to
関連付けでは、オプションの:touch
オプションを指定できます。これにより、子ドキュメントが更新されるたびに親ドキュメントが操作されるようになります。
class Band include Mongoid::Document field :name belongs_to :label, touch: true end band = Band.first band.name = "The Rolling Stones" band.save! # Calls touch on the parent label. band.touch # Calls touch on the parent label.
:touch
では、 update_at に加えて、親関連付けで操作するフィールドを指定する string またはシンボルの引数を取ることもできます。
class Label include Mongoid::Document include Mongoid::Timestamps field :bands_updated_at, type: Time has_many :bands end class Band include Mongoid::Document belongs_to :label, touch: :bands_updated_at end label = Label.create! band = Band.create!(label: label) band.touch # Updates updated_at and bands_updated_at on the label.
埋め込みドキュメントが操作されると、その親は複合ルートを介して再帰的に操作されます(埋め込みドキュメントが保存されるときにすべての親が必ず保存されるため)。 したがって、 embedded_in
の関連付けでは:touch
属性は不要です。
Mongoid は現在 、 embedded_in 関連付けで操作される追加のフィールドの指定をサポートしていません。 。
:touch
構成階層は埋め込みドキュメントのアクセス時に常に更新されるため、 embedded_in
関連付けでfalse
に設定しないでください。 これは現在は強制されていませんが 、将来 は適用される予定です 。
counter_cache オプション
ActiveRecord と同様に、関連付けに対して:counter_cache
オプションを使用すると、所有オブジェクトの数をより効率的に検索できます。 また、ActiveRecord と同様に、関連するモデルに追加の属性があることも考慮する必要があります。 つまり、Mongoid では、関連付けられたモデルにMongoid::Attributes::Dynamic
を含める必要があります。 例:
class Order include Mongoid::Document belongs_to :customer, counter_cache: true end class Customer include Mongoid::Document include Mongoid::Attributes::Dynamic has_many :orders end
関連付けプロキシ
関連付けは、ターゲット オブジェクトへの透過的なプロキシを使用します。 これにより、状況によっては予期しない動作を引き起こすことがあります。
関連付けに応じて、関連付けターゲットのメソッドにアクセスすると、メソッドの可視性が失われる場合があります。
class Order include Mongoid::Document belongs_to :customer private def internal_status 'new' end end class Customer include Mongoid::Document has_many :orders private def internal_id 42 end end order = Order.new customer = Customer.create!(orders: [order]) # has_many does not permit calling private methods on the target customer.orders.first.internal_status # NoMethodError (private method `internal_status' called for #<Order:0x000055af2ec46c50>) # belongs_to permits calling private methods on the target order.customer.internal_id # => 42
関連付けメタデータ
Mongoid のすべての関連付けには、問題の関連付けに関する情報を保持するメタデータが含まれており、サードパーティの開発者が Mongoid を拡張するために使用する価値の高いツールです。
関連付けの関連付けメタデータには、さまざまな方法でアクセスできます。
# Get the metadata for a named association from the class or document. Model.reflect_on_association(:association_name) model.reflect_on_association(:association_name) # Get the metadata with a specific association itself on a specific # document. model.associations[:association_name]
属性
すべての関連付けには、プロキシされたドキュメントである_target
、関連付けがハングするドキュメントである_base
、関連付けに関する情報を提供する_association
が含まれます。
class Person include Mongoid::Document embeds_many :addresses end person.addresses = [ address ] person.addresses._target # returns [ address ] person.addresses._base # returns person person.addresses._association # returns the association metadata
関連付けオブジェクト
関連付けオブジェクト自体には、複数の情報が含まれているため、Mongoid の拡張機能の開発者に役立ちます。
方式 | 説明 |
---|---|
Association#as | 多態的な子に親の名前を返します。 |
Association#as? | オプションとして が存在するかどうかを返します。 |
Association#autobuilding? | 関連付けが自動構築であるかどうかを返します。 |
Association#autosaving? | 関連付けが自動保存であるかどうかを返します。 |
Association#cascading_callbacks? | 関連付けに親から階層化されたコールバックがあるかどうかを返します。 |
Association#class_name | プロキシされたドキュメントのクラス名を返します。 |
Association#cyclic? | 関連付けが定期的な関連付けであるかどうかを返します。 |
Association#dependent | 関連付けの依存関係オプションを返します。 |
Association#destructive? | 関連付けに依存している削除または破棄がある場合は true を返します。 |
Association#embedded? | 関連付けが別のドキュメントに埋め込まれているかどうかを返します。 |
Association#forced_nil_inverse? | 関連付けに nil 逆が定義されているかどうかを返します。 |
Association#foreign_key | 外部キー フィールドの名前を返します。 |
Association#foreign_key_check | 外部キー フィールドのダーティ チェック メソッドの名前を返します。 |
Association#foreign_key_setter | 外部キー フィールド セッターの名前を返します。 |
Association#indexed? | 外部キーが自動インデックス化されているかどうかを返します。 |
Association#inverses | すべての逆関連付けの名前を返します。 |
Association#inverse | 単一の逆関連付けの名前を返します。 |
Association#inverse_class_name | 逆側の関連付けのクラス名を返します。 |
Association#inverse_foreign_key | 逆の側の外部キー フィールドの名前を返します。 |
Association#inverse_klass | 逆側の関連付けのクラスを返します。 |
Association#inverse_association | 関連付けのメタデータを逆側で返します。 |
Association#inverse_of | 逆関連付けの明示的に定義された名前を返します。 |
Association#inverse_setter | 逆を設定するために使用されるメソッドの名前を返します。 |
Association#inverse_type | 逆の多形タイプ フィールドの名前を返します。 |
Association#inverse_type_setter | 逆の多形型フィールド セッターの名前を返します。 |
Association#key | 関連付けを取得するために使用する属性ハッシュ内のフィールドの名前を返します。 |
Association#klass | 関連付け内のプロキシされたドキュメントのクラスを返します。 |
Association#name | 関連付け名を返します。 |
Association#options | ActiveRecord との API 互換性のために自分自身を返します。 |
Association#order | 関連付けのカスタム ソート オプションを返します。 |
Association#polymorphic? | 関連付けが多形であるかどうかを返します。 |
Association#setter | 関連付けを設定するフィールドの名前を返します。 |
Association#store_as | 埋め込み関連付けを保存する属性の名前を返します。 |
Association#touchable? | 関連付けに接続オプションがあるかどうかを返します。 |
Association#type | 多形タイプを取得するフィールドの名前を返します。 |
Association#type_setter | 多形タイプを設定するフィールドの名前を返します。 |
Association#validate? | 関連付けに関連する検証があるかどうかを返します。 |