読者です 読者をやめる 読者になる 読者になる

babie steps

作業療法記録

Rails3 対応 MongoDB ORM、Mongoid 詳解―関連

ruby rails mongodb

Mongoid::Document は、embeds_one, embeds_many, embedded_in といった、ActiveRecord スタイルの3つのマクロを通して、他のドキュメントに対して関連を設定することができます。関連を設定すると、1つのドキュメントが他のすべてのドキュメントのルートになり、全ての関連付けられたオブジェクトはルートドキュメントに埋め込まれます。リレーショナルな関連はこれらのマクロでは設定できません。後述するリレーショナルな関連の項を見てください。


先の例の Person モデルが、他のドキュメントと関連する場合を考えてみましょう。
app/models/person.rb:

class Person
  include Mongoid::Document
  field :first_name
  field :last_name
  embeds_one :address
  embeds_many :phones
end

app/models/address.rb:

class Address
  include Mongoid::Document
  field :street
  field :city
  field :state
  field :post_code
  embedded_in :person, :inverse_of => :address
end

app/models/phone.rb:

class Phone
  include Mongoid::Document
  field :country_code, :type => Integer, :default => 1
  field :number
  embedded_in :person, :inverse_of => :phones
end

上記のモデルが与えられた場合、person は、embeds_one はハッシュ、embeds_many は配列の、BSON 構造としてデータベースに保存されます。embedded_in マクロは、エンベッドがうまく働くように、必ず定義されなければなりません。忘れないで下さい。

{
  first_name: "Durran",
  last_name: "Jordan",
  address: {
    street: "30 Rockefeller Plaza",
    city: "New York",
    state: "NY",
    post_code: "10112"
  },
  phones: [
    { country_code: 1, number: "212-555-1212" },
    { country_code: 1, number: "212-555-1213" }
  ]
}


関連はオプションを取ります。最も大事な必須オプションは、embedded_in マクロにおける inverse_of オプションです。関連を適切に設定し、オブジェクトグラフが、どのオブジェクトに対するどんな変更にも確かに追従することができるように、embedded_in マクロはこのオプションを提供します。その値は親オブジェクトの関連名にしなければなりません。さらに、関連名にクラス名の単数形・複数形と違うものを使いたい場合には、class_name オプションが使用します。前述の例の Person クラスを変更して、phones という関連名を phone_numbers に変更してみましょう。

person.rb:

class Person
  include Mongoid::Document
  field :first_name
  field :last_name
  embeds_one :address
  embeds_many :phone_numbers, :class_name => "Phone"
end

関連のビルドと作成

関連は、以下のように、セット、追加、ビルド、作成することができます。

embeds_one:

person = Person.new

person.build_address(:street => "Oxford Street")
person.create_address(:street => "Oxford Street")
person.address = Address.new(:street => "Oxford Street")

embeds_many:

person = Person.new

person.phone_numbers.build(:number => "415-555-1212")
person.phone_numbers.create(:number => "415-555-1212")
person.phone_numbers << Phone.new(:number => "415-555-1212")
person.phone_numbers = [ Phone.new(:number => "415-555-1212") ]

embedded_in:

address = Address.new
address.person = Person.new(:first_name => "Mark")

ポリモーフィックな関連

デフォルトでは、embedded_in 関連は既にポリモーフィックです。どんな名前を与えようと、常に親オブジェクトを返します。「安心毛布」として、:polymorphic => true オプションを付けられますが、実際には何も行ないません。

address.rb:

class Address
  include Mongoid::Document
  field :street
  field :city
  field :state
  field :post_code
  embedded_in :addressable, :inverse_of => :address
end

この例は、address.addressable が実際には Person である親オブジェクトを返します。

関連の拡張

Mongoid は無名の関連の拡張をサポートします。@target インスタンス変数を使用して、プロクシされたターゲットにアクセスできます。

person.rb:

class Person
  include Mongoid::Document
  field :name
  embeds_many :addresses do
    def california
      @target.select { |address| address.state == "CA" }
    end
  end
end

上記の例では、person.addresses.california はカルフォルニアのアドレスのみを返します。

リレーショナルな関連

Mongoid は、他のコレクションのドキュメントや他のデータベースにあるオブジェクトへの、基本的なリレーショナルな関連をサポートします。関連付けられたオブジェクトは、関連がうまく動くように、ActiveRecord スタイルのファインダーをサポートしていなければなりません。リレーショナルな関連では、references_one, references_many, referenced_in の3つのマクロが提供されます。マクロを使用したとき、foo_id フィールドが referenced_in 側に作成されます。stored_as オプションを使ったときは、逆に bar_ids が配列として保存されます。

class Person
  include Mongoid::Document
  references_one :policy
  references_many :prescriptions
  references_many :preferences, :stored_as => :array, :inverse_of => :people
end

class Policy
  include Mongoid::Document
  referenced_in :person
end

class Prescription
  include Mongoid::Document
  referenced_in :person
end

class Preference
  include Mongoid::Document
  references_many :people, :stored_as => :array, :inverse_of => :preferences
end

person = Person.create
policy = Policy.create
prescription = Prescription.create

person.policy = policy
person.prescriptions = [prescription]

(筆者注:Mongoid 2.0.0.beta.16 では以下のようにしないと保存できませんでした。この辺はリファクタリングしている最中っぽいです。バージョンアップ時にこの記事も更新します。)

person.save #=> ダメ。
policy.save
prescription.save

p1 = Preference.create
p2 = Preference.create
person.preference_ids = [p1._ids, p2._ids]
p1.person_ids = [person._id]
p2.person_ids = [person._id]
person.save
p1.save
p2.save

カスケード削除

ActiveRecord と同じように、親オブジェクトが削除された場合に、子オブジェクトも削除したい場合は、references_one と references_many マクロに :dependent オプションを付けます。

class Person
  include Mongoid::Document
  references_one :policy, :dependent => :destroy
  references_many :prescriptions, :dependent => :delete
end


関連については以上です。