Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bernardobarreto/34195d66a9184e43a93d90ce94f4ae8a to your computer and use it in GitHub Desktop.
Save bernardobarreto/34195d66a9184e43a93d90ce94f4ae8a to your computer and use it in GitHub Desktop.
Explains how mongoid embeds_many is breaking when nesting more than 1 level deep
class Author
include Mongoid::Document
field :name, :type => String
embeds_many :posts
end
class Post
include Mongoid::Document
field :title, :type => String
embedded_in :author
embeds_many :comments
accepts_nested_attributes_for :comments
end
class Comment
include Mongoid::Document
field :text, :type => String
embedded_in :post
end
# EXAMPLE 1
# nested_attributes will not save embeds_many relations more than 1 level deep
?> author = Author.create(:name => "Bob")
=> #<Author _id: 4d3bdf2998cefdf42d000001, name: "Bob">
?> post1 = author.posts.build(:title => "My first blog post", :comments_attributes => { "1" => { "text" => "Nice post man" }, "2" => { "text" => "Spam spam spam" } })
=> #<Post _id: 4d3bdf3698cefdf42d000002, title: "My first blog post">
?> post1.comments
=> [#<Comment _id: 4d3bdf3698cefdf42d000003, text: "Nice post man">, #<Comment _id: 4d3bdf3698cefdf42d000004, text: "Spam spam spam">]
?> post1.save
=> true
?> author.reload
=> #<Author _id: 4d3bdf2998cefdf42d000001, name: "Bob">
?> author.posts.last
=> #<Post _id: 4d3bdf3698cefdf42d000002, title: "My first blog post">
?> author.posts.last.comments
=> []
# EXAMPLE 2
# Similarly, the following will fail even when not using nested_attributes.
?> post2 = author.posts.build(:title => "Another fine post")
=> #<Post _id: 4d3be00d98cefdf42d000005, title: "Another fine post">
?> post2.comments << [ Comment.new(:text => "Profound indeed"), Comment.new(:text => "More spam") ]
=> [#<Comment _id: 4d3be03098cefdf42d000006, text: "Profound indeed">, #<Comment _id: 4d3be03098cefdf42d000007, text: "More spam">]
?> post2.comments.size
=> 2
?> post2.save
=> true
?> author.reload
=> #<Author _id: 4d3bdf2998cefdf42d000001, name: "Bob">
?> author.posts.last
=> #<Post _id: 4d3be00d98cefdf42d000005, title: "Another fine post">
?> author.posts.last.comments
=> []
# Notice above that comments are being pushed onto the post before it gets persisted on author.
# This is similar to how the NestedAttributes::Many builder processes nested attributes from
# the bottom up, which often results in new objects being pushed into an un-persisted parent.
# A patch for the NestedAttributes::Many builder might look something like this.
# However, the options hash is getting lost on subsequent calls to initialize.
# Perhaps a better way would be to implement this functionality inside Array.push (i.e. <<)
module Mongoid::Relations::Builders::NestedAttributes::Many < NestedBuilder
def build(parent)
@existing = parent.send(metadata.name)
if over_limit?(attributes)
raise Errors::TooManyNestedAttributeRecords.new(existing, options[:limit])
elsif metadata.embedded? && !parent.persisted?
# If the parent is not yet persisted, call save and
# then recursively set it's nested attributes
parent.upsert
parent.send("#{metadata.name}_attributes=", @raw_attributes)
else
attributes.each { |attrs| process(attrs[1]) }
end
end
def initialize(metadata, attributes, options = {})
@raw_attributes = attributes # Probably a better way to do this
@attributes = attributes.with_indifferent_access.sort do |a, b|
a[0] <=> b[0]
end
@metadata = metadata
@options = options
end
...
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment