Docs Menu
Docs Home
/ / /
Mongoid
/

関連付け

項目一覧

  • 参照された関連付け
  • が 1 つ
  • が多い
  • に属します
  • を持ち、多くに属する
  • 参照された関連付けのクエリ
  • 埋め込み関連付け
  • 埋め込み 1
  • 埋め込みが多い
  • 再帰埋め込み
  • 参照と埋め込み
  • 埋め込み関連付けのクエリ
  • _idフィールドの省略
  • 削除
  • ハッシュ割り当て
  • 一般的な動作
  • 拡張機能
  • カスタム関連付け名
  • カスタムプライマリキーと外部キー
  • カスタムスコープ
  • 検証
  • 多形
  • 段階的なコールバック
  • 依存関係の動作
  • 自動保存
  • 存在述語
  • 自動構築
  • サこと
  • counter_cache オプション
  • 関連付けプロキシ
  • 関連付けメタデータ
  • 属性
  • 関連付けオブジェクト

Mongoid は、ActiveRecord ユーザーに慣れる、 has_onehas_manybelongs_tohas_and_belongs_to_manyの関連付けをサポートします。

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?メソッドを使用して、データベースからドキュメントセット全体を取得することなく、関連付けにドキュメントが含まれているかどうかを効率的に判断します。

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?メソッドは、関連付け内に永続化されたドキュメントがあるかどうかを判断します。 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_onehas_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_oneembeds_manyembedded_inマイクロ、および再帰埋め込み用のrecursively_embeds_onerecursively_embeds_manyを使用して定義されます。

子が親ドキュメントに埋め込まれている 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_manyembedded_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.commentcomment1に設定されていますが、再読み込みが発生したため、 post.commentcomment1と同じオブジェクトを参照しなくなりました。 つまり、1 つのオブジェクトを更新しても、他のオブジェクトは暗黙的に更新されることはありません。 これは、次の操作にとって重要です。

post.comment = comment2
post.reload

現在、 post.commentcomment2に設定され、古いコメントのpost_idnilに設定されています。 ただし、 post.commentに割り当てられた値はcomment1と同じオブジェクトを参照していなかったため、 post.commentの古い値はnil post_idになるように更新されていましたが、 comment1は引き続き を含みます。 post_idセット

post.comment = comment1
post.reload

最後に、この最後の割り当てはcomment1post_idを設定しようとします。この時点でnilである必要がありますが、古いpost_idに設定されています。 この操作中、 post_idcomment2からクリアされ、新しいpost_idcomment1に設定されます。 ただし、 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 に完全に実装されています。クエリはサーバーに送信されません。

次の演算子がサポートされています。

たとえば、先ほど指定されたモデル定義を使用して、ロードされたバンド上のツアーをクエリできます。

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のような無効な引数を許可する)。 これは、クライアント側でより厳密に検証されます。

デフォルトでは、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 つの方法cleardestroy_alldelete_allが提供されています。

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メソッドは、 $pullAll 演算子を使用して 関連付けにあるドキュメントを削除します。 clearと違い、 delete_allは次の操作を行います。

  • 関連付けがまだロードされていない場合は、関連付けをロードします。

  • アプリケーション内に存在するドキュメントのみを削除します。

delete_all は、削除されるドキュメントに対して破棄コールバックを実行しません。

例:

band = Band.find(...)
band.tours.delete_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_oneembeds_manyembedded_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パラメータを使用して、関連付けに特定のスコープを設定できます。 スコープは、関連付けの一部と見なされるオブジェクトを制限する追加のフィルターです。スコープ付きの関連付けでは、スコープ条件を満たすドキュメントのみが返されます。 スコープは次のいずれかになります。

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_onehas_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に設定しないでください。 これは現在は強制されていませんが 、将来 は適用される予定です

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?
関連付けに関連する検証があるかどうかを返します。

戻る

継承