Skip to content

Instantly share code, notes, and snippets.

@zef
Created January 12, 2010 20:49
Show Gist options
  • Save zef/275594 to your computer and use it in GitHub Desktop.
Save zef/275594 to your computer and use it in GitHub Desktop.
# This is an incomplete implementation.
module MongoMapper
module NestedAttributes
def self.included(base)
base.extend(ClassMethods)
base.send :include, InstanceMethods
end
module ClassMethods
def accepts_nested_attributes_for(*attr_names)
options = { :allow_destroy => false }
options.update(attr_names.extract_options!)
options.assert_valid_keys(:allow_destroy, :reject_if)
for association_name in attr_names
if associations.any? { |key, value| key == association_name.to_s }
module_eval %{
def #{association_name}_attributes=(attributes)
assign_nested_attributes_for_association(:#{association_name}, attributes, #{options[:allow_destroy]})
end
}, __FILE__, __LINE__
else
raise ArgumentError, "No association found for name `#{association_name}'. Has it been defined yet?"
end
end
end
end
module InstanceMethods
UNASSIGNABLE_KEYS = %w{ id _id _destroy }
def assign_nested_attributes_for_association(association_name, attributes_collection, allow_destroy)
unless attributes_collection.is_a?(Hash) || attributes_collection.is_a?(Array)
raise ArgumentError, "Hash or Array expected, got #{attributes_collection.class.name} (#{attributes_collection.inspect})"
end
if attributes_collection.is_a? Hash
attributes_collection = attributes_collection.sort_by { |index, _| index.to_i }.map { |_, attributes| attributes }
end
attributes_collection.each do |attributes|
attributes.stringify_keys!
if attributes['_id'].blank?
send(association_name) << association_name.to_s.classify.constantize.new(attributes)
elsif existing_record = send(association_name).detect { |record| record.id.to_s == attributes['_id'].to_s }
if existing_record.has_destroy_flag?(attributes) && allow_destroy
send(association_name).delete(existing_record)
existing_record.destroy unless association_name.to_s.classify.constantize.embeddable?
else
existing_record.attributes = attributes.except(*UNASSIGNABLE_KEYS)
end
end
end
end
def has_destroy_flag?(hash)
Boolean.to_mongo(hash['_destroy'])
end
end
end
end
require 'test_helper'
require 'models'
class NestedAttributesTest < Test::Unit::TestCase
def setup
Project.accepts_nested_attributes_for(:people, :collaborators, :allow_destroy => true)
Project.collection.remove
end
context "A Document" do
setup do
@project = Project.create(:name => 'Nesting Attributes',
:people_attributes => [{:name => 'Dude'}],
:collaborators_attributes => [{:name => 'Another Dude'}]
)
end
should "accept nested attributes for embedded documents" do
@project.people.size.should == 1
end
should "accept nested attributes for associated documents" do
@project.collaborators.size.should == 1
end
context "which already exists" do
setup do
person = @project.people.first.attributes.merge({:_destroy => true})
collaborator = @project.collaborators.first.attributes.merge({:_destroy => true})
# @project.update_attributes(:people_attributes => [person], :collaborators_attributes => [collaborator])
@project.attributes = {:people_attributes => [person], :collaborators_attributes => [collaborator]}
end
should "not destroy associated documents until the document is saved" do
@project.collaborators.size.should == 1
end
should "destroy embedded documents when saved" do
@project.save
@project.reload
@project.people.size.should == 0
end
should "destroy associated documents when saved" do
@project.save
@project.reload
@project.collaborators.size.should == 0
end
end
end
should "raise an ArgumentError for non existing associations" do
lambda {
Project.accepts_nested_attributes_for :blah
}.should raise_error(ArgumentError)
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment