Skip to content

Instantly share code, notes, and snippets.

@zef
Created January 12, 2010 20:49
Show Gist options
  • Select an option

  • Save zef/275594 to your computer and use it in GitHub Desktop.

Select an option

Save zef/275594 to your computer and use it in GitHub Desktop.

Revisions

  1. zef created this gist Jan 12, 2010.
    68 changes: 68 additions & 0 deletions nested_attributes.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,68 @@
    # 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
    60 changes: 60 additions & 0 deletions test_nested_attributes.rb
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,60 @@
    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