-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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