require 'spec_helper'

describe "BelongsToProxy" do
  before do
    @post_class = Doc()
    @comment_class = Doc do
      key :post_id, ObjectId
    end
    @comment_class.belongs_to :post, :class => @post_class
  end

  it "should default to nil" do
    @comment_class.new.post.nil?.should be_truthy
  end

  it "should return nil instead of a proxy" do
    nil.should === @comment_class.new.post
  end

  it "should have boolean presence method" do
    comment = @comment_class.new(:name => 'Foo!')
    comment.post?.should be_falsey

    comment.post = @post_class.new(:name => 'mongomapper')
    comment.post?.should be_truthy
  end

  it "should allow overriding association methods" do
    @comment_class.class_eval do
      def post?
        super
      end
    end

    instance = @comment_class.new
    instance.post?.should be_falsey
    instance.post = @post_class.new
    instance.post?.should be_truthy
  end

  it "should be able to replace the association" do
    post = @post_class.new(:name => 'mongomapper')
    comment = @comment_class.new(:name => 'Foo!', :post => post)
    comment.save.should be_truthy

    comment = comment.reload
    comment.post.should == post
    comment.post.nil?.should be_falsey
  end

  it "should not reload the association when replacing" do
    post = @post_class.new(:name => 'mongomapper')
    comment = @comment_class.new(:name => 'Foo!', :post => post)
    comment.post.object_id.should == post.object_id
  end

  it "should properly assign the associated object when assigning the association with create" do
    child_class = Doc('Child')
    parent_class = Doc('Parent')

    parent_class.one :child, :class => child_class
    child_class.belongs_to :parent, :class => parent_class

    child = child_class.create(:parent => parent_class.create)
    child.parent.child.should == child
  end

  it "should generate a new proxy when replacing the association" do
    post1 = @post_class.create(:name => 'post1')
    post2 = @post_class.create(:name => 'post2')

    comment = @comment_class.new(:name => 'Foo!', :post => post1)
    comment.save.should be_truthy


    comment = comment.reload
    comment.post.should == post1
    comment.post.nil?.should be_falsey

    original_post = comment.post
    original_post.name.should == 'post1'

    comment.post = post2
    comment.post.name.should == 'post2'
    original_post.name.should == 'post1'
  end

  it "should unset the association" do
    post = @post_class.new(:name => 'mongomapper')
    comment = @comment_class.new(:name => 'Foo!', :post => post)
    comment.save.should be_truthy

    comment = comment.reload
    comment.post = nil
    comment.post.nil?.should be_truthy
  end

  it "should return nil if id set but document not found" do
    id = BSON::ObjectId.new
    @comment_class.new(:name => 'Foo', :post_id => id).post.nil?.should be_truthy
  end

  it "should define foreign key if it doesn't exist" do
    @category_class = Doc()
    @post_class.belongs_to :category, :class => @category_class

    @post_class.key?(:category_id).should be_truthy
  end

  it "should not define foreign key if it already exists" do
    @category_class = Doc()
    @post_class.key :category_id, String
    @post_class.belongs_to :category, :class => @category_class

    @post_class.keys['category_id'].type.should == String
  end

  context ":dependent" do
    before do
      # FIXME: make use of already defined models
      class ::Property
        include MongoMapper::Document
      end
      Property.collection.drop

      class ::Thing
        include MongoMapper::Document
        key :name, String
      end
      Thing.collection.drop
    end

    after do
      Object.send :remove_const, 'Property' if defined?(::Property)
      Object.send :remove_const, 'Thing' if defined?(::Thing)
    end

    context "=> destroy" do
      before do
        Property.key :thing_id, ObjectId
        Property.belongs_to :thing, :dependent => :destroy
        Thing.many :properties

        @thing = Thing.create(:name => "Tree")
        @property1 = Property.create
        @property2 = Property.create
        @property3 = Property.create
        @thing.properties << @property1
        @thing.properties << @property2
        @thing.properties << @property3
      end

      it "should not execute on a belongs_to association" do
        Thing.count.should == 1
        @property1.destroy
        Thing.count.should == 1
      end
    end
  end

  context "when creating documents" do
    let(:comment) { @comment_class.create }

    before do
      @post_class.key :title, String, :required => true
      @comment_class.belongs_to :post, :class => @post_class
    end

    context "#build" do
      it "should work" do
        post = comment.build_post(:title => 'Hello, world!')
        comment.post.should be_instance_of(@post_class)
        comment.post.should be_new
        comment.post.title.should == 'Hello, world!'
        comment.post.should == post
        comment.post_id.should == post.id
      end

      it "should accept a block" do
        comment.build_post(:title => 'Hello, world!') do |post|
          post.title = "Hello world!"
        end
        comment.post.title.should == "Hello world!"
      end
    end

    context "#create" do
      it "should work" do
        post = comment.create_post(:title => 'Hello, world!')
        comment.post.should be_instance_of(@post_class)
        comment.post.should_not be_new
        comment.post.title.should == 'Hello, world!'
        comment.post.should == post
        comment.post_id.should == post.id
      end

      it "should accept a block" do
        comment.create_post(:title => 'Hello, world!') do |post|
          post.title = "Hello world!"
        end
        comment.post.title.should == "Hello world!"
      end
    end

    context "#create!" do
      it "should accept a block" do
        comment.create_post! do |post|
          post.title = "Hello world!"
        end
        comment.post.title.should == "Hello world!"
      end

      it "should raise exception if invalid" do
        lambda { comment.create_post! }.should raise_error(MongoMapper::DocumentNotValid)
      end

      it "should work if valid" do
        post = comment.create_post!(:title => 'Hello, world!')
        comment.post.should be_instance_of(@post_class)
        comment.post.should_not be_new
        comment.post.title.should == 'Hello, world!'
        comment.post.should == post
        comment.post_id.should == post.id
      end
    end
  end

  context 'autosave' do
    it 'should not be true by default' do
      @comment_class.associations[:post].options[:autosave].should_not be_truthy
    end

    it 'should save parent changes when true' do
      @comment_class.associations[:post].options[:autosave] = true

      comment = @comment_class.create
      post = comment.create_post(:title => 'Hello, world!')

      comment.post.attributes = {:title => 'Hi, world.'}
      comment.save

      post.reload.title.should == 'Hi, world.'
    end

    it 'should not save parent changes when false' do
      comment = @comment_class.create
      post = comment.create_post(:title => 'Hello, world!')

      comment.post.attributes = {:title => 'Hi, world.'}
      comment.save

      post.reload.title.should == 'Hello, world!'
    end
  end

  context "regression with reassignment" do
    it "should not infinite regress (shouldn't raise SystemStackError) when assigned to twice (when the proxy isn't around)" do
      post = @post_class.create!
      comment = @comment_class.create!(post: post)

      comment = @comment_class.find(comment.id)

      post = comment.post

      comment.post = post

      lambda do
        comment.post = post
      end.should_not raise_error
    end
  end

  context "foreign_key=" do
    it "should reset parent when foreign key is changed" do
      post = @post_class.create!
      comment = @comment_class.create!(post: post)
      another_post = @post_class.create!

      comment.post.should == post

      comment.post_id = another_post.id

      comment.post.should == another_post

      comment.post_id = nil

      comment.post.should == nil
    end

    it "should reset parent when foreign key is changed from nil" do
      post = @post_class.create!
      comment = @comment_class.create!(post: nil)

      comment.post.should == nil

      comment.post_id = post.id

      comment.post.should == post

      comment.post_id = nil

      comment.post.should == nil
    end
  end
end
