Rails3 対応 MongoDB ORM、Mongoid 詳解―永続化

今回は、データベースへの Insert、Update、Delete です。


Mongoid は ActiveRecord スタイルのデータベースへの永続化メソッドをサポートしています。永続化戦略の項は、実際に実行されるデータベースクエリについて、注意して見てください。

作成

新しいドキュメントを作成し、データベースに格納するには、Document.create や Document.create! を使います。後者は、バリデーションが失敗したときに例外を上げます。

person = Person.create(:first_name => "Syd", :last_name => "Vicious")
person = Person.create!(:first_name => "Emmanuel", :last_name => "Zorg")

保存

ドキュメントをデータベースに格納するには、Document.save または Document.save! を使います。後者は、バリデーションが失敗したときに例外を上げます。

person = Person.new(:first_name => "Syd", :last_name => "Vicious")
person.save # or person.upsert

person = Person.new(:first_name => "Emmanuel", :last_name => "Zorg")
person.save!

アトリビュートの更新

新しいアトリビュートをドキュメントに書き込み、保存するには、Document.update_attrributes と Document.update_attributes! を使います。後者は、バリデーションが失敗したときに例外を上げます。

person = Person.new(:first_name => "Syd", :last_name => "Vicious")
person.update_attributes(:first_name => "Nancy")

person = Person.new(:first_name => "Emmanuel", :last_name => "Zorg")
person.update_attributes!(:first_name => "Nancy")

削除

データベースからドキュメントを削除するには、Document#destroy、 Document#delete、Document.destroy_all、Document.delete_all を使います。最初の2つは、1つのドキュメントを削除し、後の2つは、与えた条件に合う全てのドキュメントを削除します。これらの削除メソッドは、独自に定義した全てのコールバックを無視するのに気をつけてください。これらのメソッドはまた、関連から呼ぶこともできます。

person = Person.create(:first_name => "Syd", :last_name => "Vicious")
person.destroy

person = Person.create(:first_name => "Syd", :last_name => "Vicious")
person.delete

Person.destroy_all(:conditions => { :first_name => "Syd", :last_name => "Vicious" })
Person.delete_all(:conditions => { :first_name => "Syd", :last_name => "Vicious" })

person.addresses.delete_all
person.addresses.destroy_all(:conditions => { :street => "Bond St" })

セーフモードでの永続化

Mongoid は、全体設定のオプションで、セーフモードでの永続化を提供していますが、もし、デフォルトの false にしている場合でも、永続化メソッドの前に safely を追加するだけで、1クエリ毎にセーフモードを使うことができます。

Person.safely.create(:title => "King")
Person.safely.delete_all

person.safely.save
person.safely.destroy

修飾子

Mongoid は $inc 修飾子をサポートしています。

person = Person.find(id)
person.inc(:score, 100)
person.safely.inc(:score, 100) # Update in safe mode.

永続化戦略と裏で走るデータベースクエリ

これからの例では、以下のモデルに基づいて、説明します。(裏で走るデータベースクエリの読み方については、拙作「ハンズオンで分かる MongoDB チュートリアル」を参考にしてください。)

class Person
  include Mongoid::Document
  field :first_name
  field :middle_initial
  field :last_name
  embeds_one :email
  embeds_many :addresses
end

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

class Email
  include Mongoid::Document
  field :address
  embedded_in :person, :inverse_of => :email
end

新しいドキュメントの作成

シナリオ:新しいルートドキュメントを保存する。

ドキュメントを、全てのフィールドとともに、コレクションに挿入します。

Mongoid では:

person = Person.new(:first_name => "Dudley")
person.save

MongoDB のクエリでは:

db.people.insert({ "first_name" : "Dudley" }, true);
シナリオ:新しいルートドキュメントを、子供とともに保存する。

ドキュメントを、全てのフィールドと、子要素とともに、コレクションに挿入します。save はどちらから呼んでも問題ありません。

Mongoid では:

person = Person.new(:first_name => "Dudley")
address = Address.new(:street => "Upper Street")
person.addresses << address
person.save # address.save でも同様の結果になる

MongoDB のクエリでは:

db.people.insert(
  { "first_name" : "Dudley", "addresses" : [ { "street" : "Upper Street" } ] },
  true
);
シナリオ:既に存在するルートドキュメントの中に、新しい embeds_one を保存する。

ルートドキュメントの中へ、新しいエンベッドされたアトリビュートを、アトミックに更新します。これは子要素から呼ばれないといけません。

Mongoid では:

person = Person.where(:first_name => "Dudley").first
email = Email.new(:address => "dudley@moore.com")
person.email = email
email.save

MongoDB のクエリでは :(person.id = "4baa56f1230048567300485c" だとして)

db.people.update(
  { "_id" : "4baa56f1230048567300485c" },
  { "$set" : { "email" : { "address" : "dudley@moore.com" } } },
  false,
  true
);
シナリオ:既に存在するルートドキュメントの中に、新しい embeds_many を保存する。

ルートドキュメントの中の配列へ、新しいエンベッドされたアトリビュートを、アトミックに追加します。

Mongoid では:

person = Person.where(:first_name => "Dudley").first
address = Address.new(:street => "Upper Street")
person.addresses << address
address.save

MongoDB のクエリでは: (person.id = "4baa56f1230048567300485c" だとして)

db.people.update(
  { "_id" : "4baa56f1230048567300485c" },
  { "$push" : { "addresses" : { "street" : "Upper Street" } } },
  false,
  true
);

既にあるドキュメントを更新する

既存のドキュメントを更新するとき、Mongoid のダーティーアトリビュートモジュールは、変更のあったものだけ更新するようになっています。save メソッドは、更新されたドキュメントから呼ばなければなりません。(これは今後のリリースで変更される予定です。ドキュメントツリー全体を確認してください。)

シナリオ:ルートドキュメントを更新する。

ダーティーアトリビュートを、アトミックに更新します。

Mongoid では:

person = Person.where(:first_name => "Dudley").first
person.last_name = "Moore"
person.save

MongoDB のクエリでは: (person.id = "4baa56f1230048567300485c" だとして)

db.people.update(
  { "_id" : "4baa56f1230048567300485c" },
  { "$set" : { "last_name" : "Moore" } },
  false,
  true
);
シナリオ:既にある embeds_one を更新する。

既にエンベッドされているドキュメントを、アトミックに更新します。

Mongoid では:

person = Person.where(:first_name => "Dudley").first
email = person.email
email.address = "dudley@moore.org"
email.save

MongoDB のクエリでは: (person.id = "4baa56f1230048567300485c" だとして)

db.people.update(
  { "_id" : "4baa56f1230048567300485c" },
  { "$set" : { "email.address" : "dudley@moore.org" } },
  false,
  true
);
シナリオ:既にある embeds_many を更新する。

既にエンベッドされているドキュメントを、配列のその部分だけ、アトミックに更新します。

Mongoid では:

person = Person.where(:first_name => "Dudley").first
address = person.addresses.first
address.street = "Clerkenwell Road"
address.save

MongoDB のクエリでは: (person.id = "4baa56f1230048567300485c" だとして)

db.people.update(
  { "_id" : "4baa56f1230048567300485c" },
  { "$set" : { "addresses.0.street" : "Clerkenwell Road" } },
  false,
  true
);

混合した永続化

Mongoid の永続化では、永続化コマンドが実行され、全ての更新は、その下の階層のエンベッドされたものの更新も同様に、アトミックです。このため、ドキュメントツリー全体を扱うのに便利で、1回のデータベース呼び出しで済みます。ベータ8リリースでは、削除はこういう風に動きませんので、別々に呼び出してください。

シナリオ:ルートドキュメントと、子要素を、新しい子要素を追加した上で、更新する。

Mongoid では:

person = Person.where(:first_name => "Dudley").first
person.last_name = "Moore"
person.email.address = "d.moore@gmail.com"
person.addresses.build(:street => "Upper Street")
person.save

MongoDB のクエリでは: (person.id = "4baa56f1230048567300485c" だとして)

db.people.update(
  { "_id" : "4baa56f1230048567300485c" },
  {
    "$set" :
    { "last_name" : "Moore", "email.address" : "d.moore@gmail.com" },
    "$push" :
    { "addresses" : { "street" : "Upper Street" } }
  },
  false,
  true
);

ドキュメントの削除

シナリオ:既にあるルートドキュメントを削除する。

ドキュメントの id でコレクションから削除します。

Mongoid では:

person = Person.where(:first_name => "Dudley").first
person.delete # destroy メソッドでも動きます

MongoDB のクエリでは: (person.id = "4baa56f1230048567300485c" だとして)

db.people.remove({ "_id" : "4baa56f1230048567300485c" }, true);
シナリオ:既にある embeds_one を削除する。

エンベッドされたドキュメントをアトミックに更新します。

Mongoid では:

person = Person.where(:first_name => "Dudley").first
email = person.email
email.delete # もしくは destroy

MongoDB のクエリでは: (person.id = "4baa56f1230048567300485c" だとして)

db.people.update(
  { "_id" : "4baa56f1230048567300485c" },
  { "$unset" : { "email" : true } },
  false,
  true
);
シナリオ:既にある embeds_many を削除する。

エンベッドされた配列のドキュメントをアトミックに更新します。

Mongoid では:

person = Person.where(:first_name => "Dudley").first
address = person.addresses.first
address.delete # or destroy
The MongoDB query: 
(Assume person.id = "4baa56f1230048567300485c") 
(Assume address.id = "4baa56f1230048567300485d")

MongoDB のクエリでは: (person.id = "4baa56f1230048567300485c"、address.id = "4baa56f1230048567300485d"だとして)

db.people.update(
  { "_id" : "4baa56f1230048567300485c" },
  { "$pull" : { "addresses" : { "_id" : "4baa56f1230048567300485d" } } },
  false,
  true
);


永続化は以上です。