require File.expand_path(File.dirname(__FILE__) + '/spec_helper') describe "MongoDoc::Document" do context "satisfies form_for requirements" do class FormForTest include MongoDoc::Document key :data end before do @doc = FormForTest.new @doc._id = '1' end it "#id returns the _id" do @doc.id.should == @doc._id end it "#to_param returns the _id" do @doc.to_param.should == @doc._id end context "#new_record?" do it "is true when the object does not have an _id" do @doc._id = nil @doc.should be_new_record end it "is false when the object has an id" do @doc.should_not be_new_record end end context "attributes" do it "has an initialize method that takes a hash" do data = 'data' FormForTest.new(:data => data).data.should == data end it "can set attributes from a hash" do test = FormForTest.new data = 'data' test.attributes = {:data => data} test.data.should == data end it "returns all its attributes" do data = 'data' test = FormForTest.new(:data => data) test.attributes.should == {:data => data} end end end context "validations" do class SimpleValidationTest include MongoDoc::Document key :data validates_presence_of :data end it "are included by MongoDoc::Document" do Validatable.should === SimpleValidationTest.new end it "valid? fails when a document is invalid" do doc = SimpleValidationTest.new doc.should_not be_valid doc.should have(1).error_on(:data) end end context "saving" do class SaveRoot include MongoDoc::Document has_many :save_children end class SaveChild include MongoDoc::Document key :data end before do @root = SaveRoot.new @root.stub(:_save) @child = SaveChild.new @root.save_children << @child end context "#save" do it "delegates to the root" do validate = true @root.should_receive(:save).with(validate) @child.save(validate) end context "when validating" do it "validates" do @root.should_receive(:valid?) @root.save(true) end context "and valid" do it "delegates to _save" do @root.should_receive(:_save).with(false) @root.save(true) end it "returns the result of _save if valid" do id = 'id' @root.stub(:valid?).and_return(true) @root.should_receive(:_save).and_return(id) @root.save(true).should == id end end context "and invalid" do it "does not call _save" do @root.stub(:valid?).and_return(false) @root.should_not_receive(:_save) @root.save(true) end it "returns false" do @root.stub(:valid?).and_return(false) @root.save(true).should be_false end end end context "when not validating" do it "does not validate" do @root.should_not_receive(:valid?) @root.save(false) end it "delegates to _save" do @root.should_receive(:_save).with(false) @root.save(false) end it "returns the result of _save" do id = 'id' @root.stub(:_save).and_return(id) @root.save(false).should == id end end end context "#save!" do it "delegates to the root" do @root.should_receive(:save!) @child.save! end it "validates" do @root.should_receive(:valid?).and_return(true) @root.save! end it "returns the result of _save if valid" do id = 'id' @root.stub(:valid?).and_return(true) @root.should_receive(:_save).with(true).and_return(id) @root.save!.should == id end it "raises if invalid" do @root.stub(:valid?).and_return(false) expect do @root.save! end.should raise_error(MongoDoc::DocumentInvalidError) end end end context "#_save" do class SaveTest include MongoDoc::Document end before do @collection = stub('collection') @doc = SaveTest.new @doc.stub(:_collection).and_return(@collection) end it "delegates to the collection save" do safe = true @collection.should_receive(:save) @doc.send(:_save, safe) end it "sets the _id of the document" do id = 'id' @collection.stub(:save).and_return(id) @doc.send(:_save, true) @doc._id.should == id end it "returns the _id" do id = 'id' @collection.stub(:save).and_return(id) @doc.send(:_save, true).should == id end end context "creating" do class CreateTest include MongoDoc::Document key :data validates_presence_of :data end before do @value = 'value' CreateTest.stub(:_create).and_return(true) end context ".create" do it "creates a new document" do obj = CreateTest.new CreateTest.should_receive(:new).and_return(obj) CreateTest.create end it "delegates to _create with safe => false" do obj = CreateTest.new(:data => @value) CreateTest.stub(:new).and_return(obj) CreateTest.should_receive(:_create).with(obj, false).and_return(true) CreateTest.create(:data => @value) end it "sets the passed attributes" do CreateTest.create(:data => @value).data.should == @value end it "returns a valid document" do CreateTest.should === CreateTest.create(:data => @value) end it "validates" do CreateTest.create.errors.should_not be_empty end it "returns an invalid document" do CreateTest.should === CreateTest.create end end context ".create!" do it "creates a new document" do obj = CreateTest.new CreateTest.should_receive(:new).and_return(obj) CreateTest.create! rescue nil end it "delegates to _create with safe => true" do obj = CreateTest.new(:data => @value) CreateTest.stub(:new).and_return(obj) CreateTest.should_receive(:_create).with(obj, true).and_return(true) CreateTest.create!(:data => @value) end it "sets the passed attributes" do CreateTest.create!(:data => @value).data.should == @value end it "returns a valid document" do CreateTest.should === CreateTest.create!(:data => @value) end it "raises when invalid" do expect do CreateTest.create! end.should raise_error(MongoDoc::DocumentInvalidError) end end end context "#_create" do class CreateTest include MongoDoc::Document end before do @collection = stub('collection') @collection.stub(:insert) @doc = CreateTest.new CreateTest.stub(:collection).and_return(@collection) end it "delegates to the collection insert with safe" do safe = true @collection.should_receive(:insert).with(@doc, hash_including(:safe => safe)) CreateTest.send(:_create, @doc, safe) end it "sets the _id of the document" do id = 'id' @collection.stub(:insert).and_return(id) CreateTest.send(:_create, @doc, false) @doc._id.should == id end it "returns the _id" do id = 'id' @collection.stub(:insert).and_return(id) CreateTest.send(:_create, @doc, false).should == id end end context "updating attributes" do class UpdateAttributesRoot include MongoDoc::Document has_one :update_attribute_child end class UpdateAttributesChild include MongoDoc::Document key :data end before do @data = 'data' @doc = UpdateAttributesChild.new UpdateAttributesRoot.new.update_attribute_child = @doc @attrs = {:data => @data} @path_attrs = {'update_attribute_child.data' => @data} @doc.stub(:_naive_update_attributes) end context "#update_attributes" do it "sets the attributes" do @doc.update_attributes(@attrs) @doc.data.should == @data end it "normalizes the attributes to the parent" do @doc.should_receive(:_path_to_root) @doc.update_attributes(@attrs) end it "validates" do @doc.should_receive(:valid?) @doc.update_attributes(@attrs) end it "returns false if the object is not valid" do @doc.stub(:valid?).and_return(false) @doc.update_attributes(@attrs).should be_false end context "if valid" do context "and strict" do it "delegates to _strict_update_attributes" do strict_attrs = @attrs.merge(:__strict__ => true) @doc.should_receive(:_strict_update_attributes).with(@path_attrs, false) @doc.update_attributes(strict_attrs) end end context "and naive" do it "delegates to _naive_update_attributes" do @doc.should_receive(:_naive_update_attributes).with(@path_attrs, false) @doc.update_attributes(@attrs) end end it "returns the result of _naive_update_attributes" do result = 'check' @doc.stub(:_naive_update_attributes).and_return(result) @doc.update_attributes(@attrs).should == result end end end context "#update_attributes!" do it "sets the attributes" do @doc.update_attributes!(@attrs) @doc.data.should == @data end it "normalizes the attributes to the parent" do @doc.should_receive(:_path_to_root) @doc.update_attributes!(@attrs) end it "validates" do @doc.should_receive(:valid?).and_return(true) @doc.update_attributes!(@attrs) end it "raises if not valid" do @doc.stub(:valid?).and_return(false) expect do @doc.update_attributes!(@attrs) end.should raise_error(MongoDoc::DocumentInvalidError) end context "if valid" do context "and strict" do it "delegates to _strict_update_attributes with safe == true" do strict_attrs = @attrs.merge(:__strict__ => true) @doc.should_receive(:_strict_update_attributes).with(@path_attrs, true) @doc.update_attributes!(strict_attrs) end end context "and naive" do it "delegates to _naive_update_attributes with safe == true" do @doc.should_receive(:_naive_update_attributes).with(@path_attrs, true) @doc.update_attributes!(@attrs) end end it "returns the result of _naive_update_attributes" do result = 'check' @doc.stub(:_naive_update_attributes).and_return(result) @doc.update_attributes!(@attrs).should == result end end end end context "#_naive_update_attributes" do class NaiveUpdateAttributes include MongoDoc::Document end before do @id = 'id' @attrs = {:data => 'data'} @safe = false @doc = NaiveUpdateAttributes.new @doc.stub(:_id).and_return(@id) @collection = stub('collection') @collection.stub(:update) @doc.stub(:_collection).and_return(@collection) end it "calls update on the collection without a root" do @collection.should_receive(:update).with({'_id' => @id}, MongoDoc::Query.set_modifier(@attrs), {:safe => @safe}) @doc.send(:_naive_update_attributes, @attrs, @safe) end it "with a root, calls _naive_update_attributes on the root" do root = NaiveUpdateAttributes.new @doc.stub(:_root).and_return(root) root.should_receive(:_naive_update_attributes).with(@attrs, @safe) @doc.send(:_naive_update_attributes, @attrs, @safe) end end context "#_strict_update_attributes" do class StrictUpdateAttributes include MongoDoc::Document end before do @id = 'id' @attrs = {:data => 'data'} @selector = {'selector' => 'selector'} @safe = false @doc = StrictUpdateAttributes.new @doc.stub(:_id).and_return(@id) @collection = stub('collection') @collection.stub(:update) @doc.stub(:_collection).and_return(@collection) end context "without a root" do it "calls update on the collection" do @collection.should_receive(:update).with({'_id' => @id}.merge(@selector), MongoDoc::Query.set_modifier(@attrs), :safe => @safe) @doc.send(:_strict_update_attributes, @attrs, @safe, @selector) end end context "with a root" do before do @root = StrictUpdateAttributes.new @root.stub(:_collection).and_return(@collection) @doc.stub(:_root).and_return(@root) @doc.stub(:_selector_path_to_root).and_return({'path._id' => @id}) end it "calls _selector_path_to_root on our id" do @doc.should_receive(:_selector_path_to_root).with('_id' => @id).and_return({'path._id' => @id}) @doc.send(:_strict_update_attributes, @attrs, @safe) end it "calls _strict_update_attributes on the root with our selector" do @root.should_receive(:_strict_update_attributes).with(@attrs, @safe, 'path._id' => @id) @doc.send(:_strict_update_attributes, @attrs, @safe) end end end describe "bson" do class BSONTest include MongoDoc::Document key :other end class BSONDerived < BSONTest include MongoDoc::Document key :derived end class OtherObject attr_accessor :value end before do @value = 'value' @other = OtherObject.new @other.value = @value @doc = BSONTest.new(:other => @other) end it "encodes the class for the object" do @doc.to_bson[MongoDoc::BSON::CLASS_KEY].should == BSONTest.name end it "renders a json representation of the object" do @doc.to_bson.should be_bson_eql({MongoDoc::BSON::CLASS_KEY => BSONTest.name, "other" => {MongoDoc::BSON::CLASS_KEY => OtherObject.name, "value" => @value}}) end it "includes the _id of the object" do @doc._id = Mongo::ObjectID.new @doc.to_bson.should be_bson_eql({MongoDoc::BSON::CLASS_KEY => BSONTest.name, "_id" => @doc._id.to_bson, "other" => {MongoDoc::BSON::CLASS_KEY => OtherObject.name, "value" => @value}}) end it "roundtrips the object" do MongoDoc::BSON.decode(@doc.to_bson).should be_kind_of(BSONTest) end it "ignores the class hash when the :raw_json option is used" do MongoDoc::BSON.decode(@doc.to_bson.except(MongoDoc::BSON::CLASS_KEY), :raw_json => true)['other'].should == @other.to_bson end it "allows for derived classes" do derived = BSONDerived.new(:other => @other, :derived => 'derived') MongoDoc::BSON.decode(derived.to_bson).other.should be_kind_of(OtherObject) end it "roundtrips embedded ruby objects" do MongoDoc::BSON.decode(@doc.to_bson).other.should be_kind_of(OtherObject) end context "associations" do context "has_one" do class TestHasOneBsonDoc include MongoDoc::Document has_one :subdoc end class SubHasOneBsonDoc include MongoDoc::Document key :attr end it "#to_bson renders a bson representation of the document" do doc = TestHasOneBsonDoc.new subdoc = SubHasOneBsonDoc.new(:attr => "value") bson = doc.to_bson bson["subdoc"] = subdoc.to_bson doc.subdoc = subdoc doc.to_bson.should == bson end it "roundtrips" do doc = TestHasOneBsonDoc.new subdoc = SubHasOneBsonDoc.new(:attr => "value") doc.subdoc = subdoc MongoDoc::BSON.decode(doc.to_bson).should == doc end end context "has_many" do class SubHasManyBsonDoc include MongoDoc::Document key :attr end class TestHasManyBsonDoc include MongoDoc::Document has_many :subdoc, :class_name => 'SubHasManyBsonDoc' end it "#to_bson renders a bson representation of the document" do doc = TestHasManyBsonDoc.new subdoc = SubHasManyBsonDoc.new(:attr => "value") bson = doc.to_bson bson["subdoc"] = [subdoc].to_bson doc.subdoc = subdoc doc.to_bson.should == bson end it "roundtrips" do doc = TestHasManyBsonDoc.new subdoc = SubHasManyBsonDoc.new(:attr => "value") doc.subdoc = subdoc MongoDoc::BSON.decode(doc.to_bson).should == doc end it "roundtrips the proxy" do doc = TestHasManyBsonDoc.new(:subdoc => SubHasManyBsonDoc.new(:attr => "value")) MongoDoc::Proxy.should === MongoDoc::BSON.decode(doc.to_bson).subdoc end end end end context "misc class methods" do class ClassMethods include MongoDoc::Document end it ".collection_name returns the name of the collection for this class" do ClassMethods.collection_name.should == ClassMethods.to_s.tableize.gsub('/', '.') end it ".collection returns a wrapped MongoDoc::Collection" do db = stub('db') db.should_receive(:collection).with(ClassMethods.to_s.tableize.gsub('/', '.')) MongoDoc.should_receive(:database).and_return(db) MongoDoc::Collection.should === ClassMethods.collection end end end