Help us understand the problem. What is going on with this article?

Mongoid はリレーションの子要素をいつ反映しているか

既存のドキュメントに追加する場合

has_many

以下のモデルで考える。

app/models/article.rb
class Article
  include Mongoid::Document

  field :title
  field :author
  has_many :comments, class_name: 'Comment'
end
app/models/comment.rb
class Comment
  include Mongoid::Document

  field :author
  field :text

  belongs_to :article
end

上記のモデルを利用して、以下の手順を行う。

  1. Article のデータを1件作る
  2. Comment のデータを1件作る
  3. 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):004article.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

モデルを以下の形に変更する。

app/models/article.rb
class Article
  include Mongoid::Document

  field :title
  has_one :author
  has_many :comments, class_name: 'Comment'
end
app/models/author.rb
class Author
  include Mongoid::Document

  field :name
  field :age

  belongs_to :article
end

上記のモデルを利用して、以下の手順を行う。

  1. Article のデータを1件作る
  2. Author のデータを1件作る
  3. 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 の検証分も変更している)

app/models/article.rb
class Article
  include Mongoid::Document

  field :title
  embeds_one :author
  embeds_many :comments, class_name: 'Comment'
end
app/models/comment.rb
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 にしておく。

app/models/author.rb
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.commentscomment を追加した状態で 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.commentscomment を格納してから保存することで、どちらも 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_* であれば親要素、子要素のどちらを保存しても両方が保存される。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした