既存のドキュメントに追加する場合
has_many
以下のモデルで考える。
class Article
include Mongoid::Document
field :title
field :author
has_many :comments, class_name: 'Comment'
end
class Comment
include Mongoid::Document
field :author
field :text
belongs_to :article
end
上記のモデルを利用して、以下の手順を行う。
- Article のデータを1件作る
- Comment のデータを1件作る
- Article のデータの comments に 2. で作ったデータを格納する
irb(main):001:0> article = Article.create(title: 'test article', author: 'John')
=> #<Article _id: 5def694121b6206de44b31f5, title: "test article", author: "John">
irb(main):002:0> comment = Comment.create(author: 'Tom', text: 'comment text')
=> #<Comment _id: 5def697321b6206de44b31f6, author: "Tom", text: "comment text", article_id: nil>
rb(main):003:0> comment
=> #<Comment _id: 5def697321b6206de44b31f6, author: "Tom", text: "comment text", article_id: nil>
irb(main):004:0> article.comments << comment
MONGODB | [8] localhost:28001 #1 | sandbox.insert | STARTED | {"insert"=>"comments", "ordered"=>true, "documents"=>[{"_id"=>BSON::ObjectId('5def697321b6206de44b31f6'), "author"=>"Tom", "text"=>"comment text", "article_id"=>BSON::ObjectId('5def694121b6206de44b31f5')}], "lsid"=>{"id"=><BSON::Binary:0x7034371467232...
MONGODB | [8] localhost:28001 | sandbox.insert | SUCCEEDED | 0.001s
MONGODB | [9] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"comments", "filter"=>{"article_id"=>BSON::ObjectId('5def694121b6206de44b31f5')}, "lsid"=>{"id"=><BSON::Binary:0x70343714672320 type=uuid data=0x425a912cb15c4242...>}}
MONGODB | [9] localhost:28001 | sandbox.find | SUCCEEDED | 0.001s
=> [#<Comment _id: 5def697321b6206de44b31f6, author: "Tom", text: "comment text", article_id: BSON::ObjectId('5def694121b6206de44b31f5')>]
上記の irb(main):003
の時点ででは MongoDB 上は保存されていない。
irb(main):004
で article.comments
に格納した瞬間に insert される。
> db.comments.find() // irb(main):003 のあと
> db.comments.find() // irb(main):004 のあと
{ "_id" : ObjectId("5def697321b6206de44b31f6"), "author" : "Tom", "text" : "comment text", "article_id" : ObjectId("5def694121b6206de44b31f5") }
has_one
モデルを以下の形に変更する。
class Article
include Mongoid::Document
field :title
has_one :author
has_many :comments, class_name: 'Comment'
end
class Author
include Mongoid::Document
field :name
field :age
belongs_to :article
end
上記のモデルを利用して、以下の手順を行う。
- Article のデータを1件作る
- Author のデータを1件作る
- Article のデータの author に 2. で作ったデータを格納する
irb(main):001:0> article = Article.create(title: 'second article')
=> #<Article _id: 5def73cb21b620e0c14b31f5, title: "second article">
irb(main):002:0> author = Author.create(name: 'Tom', age: 20)
=> #<Author _id: 5def73e521b620e0c14b31f6, name: "Tom", age: 20, article_id: nil>
irb(main):003:0> article.author = author
MONGODB | [8] localhost:28001 #1 | sandbox.insert | STARTED | {"insert"=>"authors", "ordered"=>true, "documents"=>[{"_id"=>BSON::ObjectId('5def73e521b620e0c14b31f6'), "name"=>"Tom", "age"=>20, "article_id"=>BSON::ObjectId('5def73cb21b620e0c14b31f5')}], "lsid"=>{"id"=><BSON::Binary:0x70343916007920 type=uuid data...
MONGODB | [8] localhost:28001 | sandbox.insert | SUCCEEDED | 0.010s
=> #<Author _id: 5def73e521b620e0c14b31f6, name: "Tom", age: 20, article_id: BSON::ObjectId('5def73cb21b620e0c14b31f5')>
has_many の例と同様に article.author = author
のタイミングで authors にドキュメントが書き込まれている。
> db.authors.find() // irb(main):002 のあと
> db.authors.find() // irb(main):003 のあと
{ "_id" : ObjectId("5def73e521b620e0c14b31f6"), "name" : "Tom", "age" : 20, "article_id" : ObjectId("5def73cb21b620e0c14b31f5") }
embeds_many
モデルを以下のように変更する。
(まとめて embeds_one の検証分も変更している)
class Article
include Mongoid::Document
field :title
embeds_one :author
embeds_many :comments, class_name: 'Comment'
end
class Comment
include Mongoid::Document
field :author
field :text
embedded_in :article
end
先程と同様の手順を行う。
irb(main):001:0> article = Article.create(title: 'third article')
=> #<Article _id: 5def78c221b62011e762e62c, title: "third article">
irb(main):002:0> comment = Comment.create(author: 'Ken', text: 'new comment')
Traceback (most recent call last):
1: from (irb):3
Mongoid::Errors::NoParent ()
message:
Cannot persist embedded document Comment without a parent document.
summary:
If the document is embedded, in order to be persisted it must always have a reference to its parent document. This is most likely caused by either calling Comment.create or Comment.create! without setting the parent document as an attribute.
resolution:
Ensure that you've set the parent relation if instantiating the embedded document directly, or always create new embedded documents via the parent relation.
irb(main):003:0> comment = Comment.new(author: 'Ken', text: 'new comment')
=> #<Comment _id: 5def792a21b62011e762e62d, author: "Ken", text: "new comment">
irb(main):004:0> article.comments << comment
MONGODB | [8] localhost:28001 #1 | sandbox.update | STARTED | {"update"=>"articles", "ordered"=>true, "updates"=>[{"q"=>{"_id"=>BSON::ObjectId('5def78c221b62011e762e62c')}, "u"=>{"$push"=>{"comments"=>{"_id"=>BSON::ObjectId('5def792a21b62011e762e62d'), "author"=>"Ken", "text"=>"new comment"}}}}], "lsid"=>{"id"=>...
MONGODB | [8] localhost:28001 | sandbox.update | SUCCEEDED | 0.002s
=> [#<Comment _id: 5def792a21b62011e762e62d, author: "Ken", text: "new comment">]
Comment.create()
はエラーになる。
embedded_in
要素があるため、親が指定されている必要があるからだ。
これも article.comments << comment
したタイミングで article に対して更新が走っている。
embeds_one
author.rb も embedded_in にしておく。
class Author
include Mongoid::Document
field :name
field :age
embedded_in :article
end
これも同様の手順で。
irb(main):001:0> article = Article.create(title: 'fourth article')
=> #<Article _id: 5def7a8721b620366a62e62c, title: "fourth article">
irb(main):002:0> author = Author.create(name: 'Sam', age: 22) # 先と同様にエラーになる。エラーは省略。
irb(main):003:0> author = Author.new(name: 'Sam', age: 22)
=> #<Author _id: 5def7ab121b620366a62e62d, name: "Sam", age: 22>
irb(main):004:0> article.author = author
MONGODB | [8] localhost:28001 #1 | sandbox.update | STARTED | {"update"=>"articles", "ordered"=>true, "updates"=>[{"q"=>{"_id"=>BSON::ObjectId('5def7a8721b620366a62e62c')}, "u"=>{"$set"=>{"author"=>{"_id"=>BSON::ObjectId('5def7ab121b620366a62e62d'), "name"=>"Sam", "age"=>22}}}}], "lsid"=>{"id"=><BSON::Binary:0x7...
MONGODB | [8] localhost:28001 | sandbox.update | SUCCEEDED | 0.002s
=> #<Author _id: 5def7ab121b620366a62e62d, name: "Sam", age: 22>
has_one と同様に article.author = author
のタイミングで author にドキュメントが書き込まれている。
新規のデータに追加する場合
モデルは全て上記のそれぞれと同様の形とする。
やることはだいたい一緒なのでここからはコンソールでの操作とコメントのみ。
has_many
irb(main):001:0> article = Article.new(title: 'fifth article', author: 'John')
=> #<Article _id: 5def7ef821b6206f8662e62c, title: "fifth article", author: "John">
irb(main):002:0> comment = Comment.create(author: 'Ken', text: 'next comment')
=> #<Comment _id: 5def7f1621b6206f8662e62d, author: "Ken", text: "next comment", article_id: nil>
irb(main):003:0> article.comments << comment # ここではどちらも保存されない
=> [#<Comment _id: 5def7f1621b6206f8662e62d, author: "Ken", text: "next comment", article_id: BSON::ObjectId('5def7ef821b6206f8662e62c')>]
irb(main):004:0> Article.where(title: 'fifth article', author: 'John').first # まだないことの確認
MONGODB | [8] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"articles", "filter"=>{"title"=>"fifth article", "author"=>"John"}, "sort"=>{"_id"=>1}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70162852966080 type=uuid data=0xa7f9242254954cfb...>}}
MONGODB | [8] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> nil
irb(main):005:0> article.save
MONGODB | [9] localhost:28001 #1 | sandbox.insert | STARTED | {"insert"=>"articles", "ordered"=>true, "documents"=>[{"_id"=>BSON::ObjectId('5def7ef821b6206f8662e62c'), "title"=>"fifth article", "author"=>"John"}], "lsid"=>{"id"=><BSON::Binary:0x70162852966080 type=uuid data=0xa7f9242254954cfb...>}}
MONGODB | [9] localhost:28001 | sandbox.insert | SUCCEEDED | 0.002s
=> true
irb(main):006:0> Article.where(title: 'fifth article', author: 'John').first # 保存されたことの確認
MONGODB | [10] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"articles", "filter"=>{"title"=>"fifth article", "author"=>"John"}, "sort"=>{"_id"=>1}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70162852966080 type=uuid data=0xa7f9242254954cfb...>}}
MONGODB | [10] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Article _id: 5def7ef821b6206f8662e62c, title: "fifth article", author: "John">
irb(main):007:0> Comment.where(author: 'Ken', text: 'next comment').first # comment が保存されていないことの確認
MONGODB | [11] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"comments", "filter"=>{"author"=>"Ken", "text"=>"next comment"}, "sort"=>{"_id"=>1}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70162852966080 type=uuid data=0xa7f9242254954cfb...>}}
MONGODB | [11] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> nil
irb(main):008:0> comment.save
MONGODB | [12] localhost:28001 #1 | sandbox.insert | STARTED | {"insert"=>"comments", "ordered"=>true, "documents"=>[{"_id"=>BSON::ObjectId('5def7f1621b6206f8662e62d'), "author"=>"Ken", "text"=>"next comment", "article_id"=>BSON::ObjectId('5def7ef821b6206f8662e62c')}], "lsid"=>{"id"=><BSON::Binary:0x7016285296608...
MONGODB | [12] localhost:28001 | sandbox.insert | SUCCEEDED | 0.002s
=> true
irb(main):009:0> Comment.where(author: 'Ken', text: 'next comment').first # 保存されたことの確認
MONGODB | [13] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"comments", "filter"=>{"author"=>"Ken", "text"=>"next comment"}, "sort"=>{"_id"=>1}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70162852966080 type=uuid data=0xa7f9242254954cfb...>}}
MONGODB | [13] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Comment _id: 5def7f1621b6206f8662e62d, author: "Ken", text: "next comment", article_id: BSON::ObjectId('5def7ef821b6206f8662e62c')>
irb(main):010:0> Article.where(title: 'fifth article', author: 'John').first.comments # リレーションが成立していることの確認
MONGODB | [14] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"articles", "filter"=>{"title"=>"fifth article", "author"=>"John"}, "sort"=>{"_id"=>1}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70162852966080 type=uuid data=0xa7f9242254954cfb...>}}
MONGODB | [14] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
MONGODB | [15] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"comments", "filter"=>{"article_id"=>BSON::ObjectId('5def7ef821b6206f8662e62c')}, "lsid"=>{"id"=><BSON::Binary:0x70162852966080 type=uuid data=0xa7f9242254954cfb...>}}
MONGODB | [15] localhost:28001 | sandbox.find | SUCCEEDED | 0.001s
=> [#<Comment _id: 5def7f1621b6206f8662e62d, author: "Ken", text: "next comment", article_id: BSON::ObjectId('5def7ef821b6206f8662e62c')>]
article.comments
に comment
を追加した状態で article
を保存しても comment
の方は保存されない。
両方を保存するとリレーションが成立している状態になる。
ちなみに、先に comment
を保存しても article
は保存されていない状態になる。
そのため、 MongoDB 側では comment
が持っている article_id
に対応する articles
コレクションが存在しない状態になる。
has_one
irb(main):001:0> article = Article.new(title: 'sixth article')
=> #<Article _id: 5def815721b62004d962e62c, title: "sixth article">
irb(main):002:0> author = Author.create(name: 'Kenny', age: 21)
=> #<Author _id: 5def816421b62004d962e62d, name: "Kenny", age: 21, article_id: nil>
irb(main):003:0> article.author = author
=> #<Author _id: 5def816421b62004d962e62d, name: "Kenny", age: 21, article_id: BSON::ObjectId('5def815721b62004d962e62c')>
irb(main):004:0> Article.where(title: 'sixth article').first
MONGODB | [7] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"articles", "filter"=>{"title"=>"sixth article"}, "sort"=>{"_id"=>1}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70163658507040 type=uuid data=0xab53b65883964307...>}}
MONGODB | [7] localhost:28001 | sandbox.find | SUCCEEDED | 0.001s
=> nil
irb(main):005:0> article.save
MONGODB | [8] localhost:28001 #1 | sandbox.insert | STARTED | {"insert"=>"articles", "ordered"=>true, "documents"=>[{"_id"=>BSON::ObjectId('5def815721b62004d962e62c'), "title"=>"sixth article"}], "lsid"=>{"id"=><BSON::Binary:0x70163658507040 type=uuid data=0xab53b65883964307...>}}
MONGODB | [8] localhost:28001 | sandbox.insert | SUCCEEDED | 0.001s
=> true
irb(main):006:0> Article.where(title: 'sixth article').first # article は保存されている
MONGODB | [9] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"articles", "filter"=>{"title"=>"sixth article"}, "sort"=>{"_id"=>1}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70163658507040 type=uuid data=0xab53b65883964307...>}}
MONGODB | [9] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Article _id: 5def815721b62004d962e62c, title: "sixth article">
irb(main):007:0> Author.where(name: 'Kenny', age: 21).first # author は保存されていない
MONGODB | [10] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"authors", "filter"=>{"name"=>"Kenny", "age"=>21}, "sort"=>{"_id"=>1}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70163658507040 type=uuid data=0xab53b65883964307...>}}
MONGODB | [10] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> nil
irb(main):008:0> article.author.save
MONGODB | [11] localhost:28001 #1 | sandbox.insert | STARTED | {"insert"=>"authors", "ordered"=>true, "documents"=>[{"_id"=>BSON::ObjectId('5def816421b62004d962e62d'), "name"=>"Kenny", "age"=>21, "article_id"=>BSON::ObjectId('5def815721b62004d962e62c')}], "lsid"=>{"id"=><BSON::Binary:0x70163658507040 type=uuid da...
MONGODB | [11] localhost:28001 | sandbox.insert | SUCCEEDED | 0.002s
=> true
irb(main):009:0> Author.where(name: 'Kenny', age: 21).first
MONGODB | [12] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"authors", "filter"=>{"name"=>"Kenny", "age"=>21}, "sort"=>{"_id"=>1}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70163658507040 type=uuid data=0xab53b65883964307...>}}
MONGODB | [12] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
=> #<Author _id: 5def816421b62004d962e62d, name: "Kenny", age: 21, article_id: BSON::ObjectId('5def815721b62004d962e62c')>
irb(main):010:0> Article.where(title: 'sixth article').first.author # リレーションが成立していることの確認
MONGODB | [13] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"articles", "filter"=>{"title"=>"sixth article"}, "sort"=>{"_id"=>1}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70163658507040 type=uuid data=0xab53b65883964307...>}}
MONGODB | [13] localhost:28001 | sandbox.find | SUCCEEDED | 0.002s
MONGODB | [14] localhost:28001 #1 | sandbox.find | STARTED | {"find"=>"authors", "filter"=>{"article_id"=>BSON::ObjectId('5def815721b62004d962e62c')}, "limit"=>1, "singleBatch"=>true, "lsid"=>{"id"=><BSON::Binary:0x70163658507040 type=uuid data=0xab53b65883964307...>}}
MONGODB | [14] localhost:28001 | sandbox.find | SUCCEEDED | 0.001s
=> #<Author _id: 5def816421b62004d962e62d, name: "Kenny", age: 21, article_id: BSON::ObjectId('5def815721b62004d962e62c')>
has_many と同様。
embeds_many
irb(main):001:0> article = Article.new(title: 'seventh article')
=> #<Article _id: 5def82ee21b620184662e62c, title: "seventh article">
irb(main):002:0> comment = Comment.new(author: 'Lita', text: 'wild comment')
=> #<Comment _id: 5def832421b620184662e62d, author: "Lita", text: "wild comment">
irb(main):003:0> article.comments << comment
=> [#<Comment _id: 5def832421b620184662e62d, author: "Lita", text: "wild comment">]
irb(main):004:0> article.save # comment.save でも同じ挙動をする
MONGODB | [7] localhost:28001 #1 | sandbox.insert | STARTED | {"insert"=>"articles", "ordered"=>true, "documents"=>[{"_id"=>BSON::ObjectId('5def82ee21b620184662e62c'), "title"=>"seventh article", "comments"=>[{"_id"=>BSON::ObjectId('5def832421b620184662e62d'), "author"=>"Lita", "text"=>"wild comment"}]}], "lsid"...
MONGODB | [7] localhost:28001 | sandbox.insert | SUCCEEDED | 0.001s
=> true
article.comments
に comment
を格納してから保存することで、どちらも DB 上反映されている状態になる。
embeds_one
irb(main):001:0> article = Article.new(title: 'eighth article')
=> #<Article _id: 5df0561821b6205f2062e62c, title: "eighth article">
irb(main):002:0> author = Author.new(name: 'Alex', age: 27)
=> #<Author _id: 5df0563221b6205f2062e62d, name: "Alex", age: 27>
irb(main):003:0> article.author = author
=> #<Author _id: 5df0563221b6205f2062e62d, name: "Alex", age: 27>
irb(main):004:0> article.save
MONGODB | [7] localhost:28001 #1 | sandbox.insert | STARTED | {"insert"=>"articles", "ordered"=>true, "documents"=>[{"_id"=>BSON::ObjectId('5df0561821b6205f2062e62c'), "title"=>"eighth article", "author"=>{"_id"=>BSON::ObjectId('5df0563221b6205f2062e62d'), "name"=>"Alex", "age"=>27}}], "lsid"=>{"id"=><BSON::Bina...
MONGODB | [7] localhost:28001 | sandbox.insert | SUCCEEDED | 0.001s
=> true
embeds_many と同様。
まとめ
親要素のドキュメントが既に MongoDB にあれば has_*
, embeds_*
のいずれも、親要素に子要素を代入/格納したタイミングで対応するドキュメントに対して update が走る。
親要素のドキュメントがまだ MongoDB に insert されていない場合、リレーションが has_*
であれば親要素になるデータを save しても子要素は保存されず、別途保存する必要がある。 embeds_*
であれば親要素、子要素のどちらを保存しても両方が保存される。