require "spec_helper" describe Mongoid::Document do before do @database = mock @collection = stub(:name => "people") @canvas_collection = stub(:name => "canvases") Mongoid::Collection.stubs(:new).with(Person, "people").returns(@collection) Mongoid::Collection.stubs(:new).with(Canvas, "canvases").returns(@canvas_collection) @collection.stubs(:create_index).with(:_type, false) @canvas_collection.stubs(:create_index).with(:_type, false) end after do Person._collection = nil Canvas._collection = nil end describe "#==" do context "when other object is a Document" do context "when attributes are equal" do before do @document = Person.new(:_id => 1, :title => "Sir") @other = Person.new(:_id => 1, :title => "Sir") end it "returns true" do @document.should == @other end end context "when attributes are not equal" do before do @document = Person.new(:title => "Sir") @other = Person.new(:title => "Madam") end it "returns false" do @document.should_not == @other end end end context "when other object is not a Document" do it "returns false" do Person.new.==("Test").should be_false end end context "when comapring parent to its subclass" do it "returns false" do Canvas.new.should_not == Firefox.new end end end describe "#eql?" do context "when other object is a Document" do context "when attributes are equal" do before do @document = Person.new(:_id => 1, :title => "Sir") @other = Person.new(:_id => 1, :title => "Sir") end it "returns true" do @document.eql?(@other).should be_true end end context "when attributes are not equal" do before do @document = Person.new(:title => "Sir") @other = Person.new(:title => "Madam") end it "returns false" do @document.eql?(@other).should_not be_true end end end context "when other object is not a Document" do it "returns false" do Person.new.eql?("Test").should be_false end end context "when comapring parent to its subclass" do it "returns false" do Canvas.new.eql?(Firefox.new).should_not be_true end end end describe "#hash" do before do @document = Person.new(:_id => 1, :title => "Sir") @other = Person.new(:_id => 2, :title => "Sir") end it "deligates to id" do @document.hash.should == @document.id.hash end it "has unique hash per id" do @document.hash.should_not == @other.hash end end describe "#alias_method_chain" do context "on a field setter" do before do @person = Person.new end it "chains the method properly" do @person.score = 10 @person.rescored.should == 30 end end end describe "#assimilate" do before do @child = Name.new(:first_name => "Hank", :last_name => "Moody") @parent = Person.new(:title => "Mr.") @options = Mongoid::Associations::Options.new(:name => :name) end it "sets up all associations in the object graph" do @child.assimilate(@parent, @options) @parent.name.should == @child end end describe ".db" do before do @db = stub @collection.expects(:db).returns(@db) end it "returns the database from the collection" do Person.db.should == @db end end describe "#clone" do before do @comment = Comment.new(:text => "Woooooo") @clone = @comment.clone end it "returns a new document sans id and versions" do @clone.id.should_not == @comment.id @clone.versions.should be_empty end end describe ".collection" do before do @person = Person.new end it "sets the collection name to the class pluralized" do Person.collection.name.should == "people" end context "when document is embedded" do before do @address = Address.new end it "raises an error" do lambda { Address.collection }.should raise_error end end end describe ".collection_name=" do context "on a parent class" do it "sets the collection name on the document class" do Patient.collection_name = "pats" Patient.collection_name.should == "pats" end end context "on a subclass" do after do Canvas.collection_name = "canvases" end it "sets the collection name for the entire hierarchy" do Firefox.collection_name = "browsers" Canvas.collection_name.should == "browsers" end end end describe ".embedded" do context "when the document is embedded" do it "returns true" do address = Address.new address.embedded.should be_true end end context "when the document is not embedded" do it "returns false" do person = Person.new person.embedded.should be_false end end context "when a subclass is embedded" do it "returns true" do circle = Circle.new circle.embedded.should be_true end end end describe ".hereditary" do context "when the class is part of a hierarchy" do it "returns true" do Canvas.hereditary.should be_true end end context "when the class is not part of a hierarchy" do it "returns false" do Game.hereditary.should be_false end end end describe ".human_name" do it "returns the class name underscored and humanized" do MixedDrink.human_name.should == "Mixed drink" end end describe ".initialize" do context "when passed a block" do it "yields self to the block" do person = Person.new do |p| p.title = "Sir" p.age = 60 end person.title.should == "Sir" person.age.should == 60 end end context "with no attributes" do it "sets default attributes" do person = Person.new person.attributes.empty?.should be_false person.age.should == 100 person.blood_alcohol_content.should == 0.0 end end context "with nil attributes" do before do @person = Person.new(nil) end it "sets default attributes" do @person.attributes.empty?.should be_false @person.age.should == 100 @person.blood_alcohol_content.should == 0.0 end end context "with attributes" do before do @attributes = { :_id => "1", :title => "value", :age => "30", :terms => "true", :name => { :_id => "2", :first_name => "Test", :last_name => "User" }, :addresses => [ { :_id => "3", :street => "First Street" }, { :_id => "4", :street => "Second Street" } ] } end it "sets the attributes hash on the object properly casted" do person = Person.new(@attributes) person.attributes[:age].should == 30 person.attributes[:terms].should be_true end end context "with a primary key" do context "when the value for the key exists" do before do Address.key :street @address = Address.new(:street => "Test") end it "sets the primary key" do @address.id.should == "test" end end end context "without a type specified" do it "sets the type" do Person.new._type.should == "Person" end end end describe ".instantiate" do context "when attributes have an id" do before do @attributes = { "_id" => "1", "_type" => "Person", "title" => "Sir", "age" => 30 } end it "sets the attributes directly" do person = Person.instantiate(@attributes) person._id.should == "1" person._type.should == "Person" person.title.should == "Sir" person.age.should == 30 end end context "with nil attributes" do it "sets the attributes directly" do person = Person.instantiate(nil) person.id.should_not be_nil end end end describe ".key" do context "when key is single field" do before do Address.key :street @address = Address.new(:street => "Testing Street Name") @address.expects(:collection).returns(@collection) @collection.expects(:save) end it "adds the callback for primary key generation" do @address.save @address.id.should == "testing-street-name" end end context "when key is composite" do before do Address.key :street, :post_code @address = Address.new(:street => "Testing Street Name", :post_code => "94123") @address.expects(:collection).returns(@collection) @collection.expects(:save) end it "combines all fields" do @address.save @address.id.should == "testing-street-name-94123" end end context "when key is on a subclass" do before do Firefox.key :name end it "sets the key for the entire hierarchy" do Canvas.primary_key.should == [:name] end end end describe "#new_record?" do context "when the object has been saved" do before do @person = Person.new(:_id => "1") end it "returns false" do @person.new_record?.should be_false end end context "when the object has not been saved" do before do @person = Person.new end it "returns true" do @person.new_record?.should be_true end end end describe "#persisted?" do before do @person = Person.new end it "delegates to new_record?" do @person.persisted?.should be_false end end describe "#_parent" do before do @attributes = { :title => "Sir", :addresses => [ { :street => "Street 1" }, { :street => "Street 2" } ] } @person = Person.new(@attributes) end context "when document is embedded" do it "returns the parent document" do @person.addresses.first._parent.should == @person end end context "when document is root" do it "returns nil" do @person._parent.should be_nil end end end describe "#parentize" do before do @parent = Person.new @child = Name.new end it "sets the parent on each element" do @child.parentize(@parent, :child) @child._parent.should == @parent end end describe "#reload" do before do @attributes = { "title" => "Herr" } @person = Person.new(:_id => Mongo::ObjectID.new.to_s) @collection.expects(:find_one).with(:_id => @person.id).returns(@attributes) end it "reloads the object attribtues from the database" do @person.reload @person.attributes.should == @attributes end it 'should return a person object' do @person.reload.should be_kind_of(Person) end end describe "#remove" do context "when removing an element from a has many" do before do @person = Person.new @address = Address.new(:street => "Testing") @person.addresses << @address end it "removes the child document attributes" do @person.remove(@address) @person.addresses.size.should == 0 end end context "when removing a has one" do before do @person = Person.new @name = Name.new(:first_name => "Neytiri") @person.name = @name end it "removes the child document attributes" do @person.remove(@name) @person.name.should be_nil end end end describe "#_root" do before do @person = Person.new(:title => "Mr") @phone_number = Phone.new(:number => "415-555-1212") @country_code = CountryCode.new(:code => 1) @phone_number.country_code = @country_code @person.phone_numbers << @phone_number end context "when document is the root" do it "returns self" do @person._root.should == @person end end context "when document is embedded one level" do it "returns the parent" do @phone_number._root.should == @person end end context "when document is embedded multiple levels" do it "returns the top level parent" do @country_code._root.should == @person end end end describe ".store_in" do context "on a parent class" do it "sets the collection name and collection for the document" do Mongoid::Collection.expects(:new).with(Patient, "population").returns(@collection) Patient.store_in :population Patient.collection_name.should == "population" end end context "on a subclass" do after do Mongoid::Collection.expects(:new).with(Firefox, "canvases") Firefox.store_in :canvases end it "changes the collection name for the entire hierarchy" do Mongoid::Collection.expects(:new).with(Firefox, "browsers").returns(@collection) Firefox.store_in :browsers Canvas.collection_name.should == "browsers" end end end describe "._types" do it "returns all subclasses for the class plus the class" do types = Canvas._types types.size.should == 3 types.should include("Firefox") types.should include("Browser") types.should include("Canvas") end it "does not return parent classes" do types = Browser._types types.size.should == 2 types.should include("Firefox") types.should include("Browser") end end describe "#to_a" do it "returns an array with the document in it" do person = Person.new person.to_a.should == [ person ] end end describe "#to_param" do it "returns the id" do id = Mongo::ObjectID.new.to_s Person.new(:_id => id).to_param.should == id.to_s end end context "validations" do context "when defining using macros" do after do Person.validations.clear end describe "#validates_acceptance_of" do it "adds the acceptance validation" do Person.class_eval do validates_acceptance_of :terms end Person.validations.first.should be_a_kind_of(Validatable::ValidatesAcceptanceOf) end end describe "#validates_associated" do it "adds the associated validation" do Person.class_eval do validates_associated :name end Person.validations.first.should be_a_kind_of(Validatable::ValidatesAssociated) end end describe "#validates_format_of" do it "adds the format validation" do Person.class_eval do validates_format_of :title, :with => /[A-Za-z]/ end Person.validations.first.should be_a_kind_of(Validatable::ValidatesFormatOf) end end describe "#validates_length_of" do it "adds the length validation" do Person.class_eval do validates_length_of :title, :minimum => 10 end Person.validations.first.should be_a_kind_of(Validatable::ValidatesLengthOf) end end describe "#validates_numericality_of" do it "adds the numericality validation" do Person.class_eval do validates_numericality_of :age end Person.validations.first.should be_a_kind_of(Validatable::ValidatesNumericalityOf) end end describe "#validates_presence_of" do it "adds the presence validation" do Person.class_eval do validates_presence_of :title end Person.validations.first.should be_a_kind_of(Validatable::ValidatesPresenceOf) end end describe "#validates_uniqueness_of" do it "adds the uniqueness validation" do Person.class_eval do validates_uniqueness_of :title end Person.validations.first.should be_a_kind_of(Validatable::ValidatesUniquenessOf) end end describe "#validates_inclusion_of" do it "adds the inclusion validation" do Person.class_eval do validates_inclusion_of :title, :within => ["test"] end Person.validations.first.should be_a_kind_of(Validatable::ValidatesInclusionOf) end end describe "#validates_exclusion_of" do it "adds the exclusion validation" do Person.class_eval do validates_exclusion_of :title, :within => ["test"] end Person.validations.first.should be_a_kind_of(Validatable::ValidatesExclusionOf) end end describe "#validates_true_for" do it "adds the true validation" do Person.class_eval do validates_true_for :title, :logic => lambda { title == "Esquire" } end Person.validations.first.should be_a_kind_of(Validatable::ValidatesTrueFor) end end end context "when running validations" do before do @person = Person.new @canvas = Canvas.new @firefox = Firefox.new end after do Person.validations.clear Canvas.validations.clear Firefox.validations.clear end describe "#validates_acceptance_of" do it "fails if field not accepted" do Person.class_eval do validates_acceptance_of :terms end @person.valid?.should be_false @person.errors.on(:terms).should_not be_nil end end describe "#validates_associated" do context "when association is a has_many" do it "fails when any association fails validation" do Person.class_eval do validates_associated :addresses end Address.class_eval do validates_presence_of :street end @person.addresses << Address.new @person.valid?.should be_false @person.errors.on(:addresses).should_not be_nil end end context "when association is a has_one" do context "when the associated is not nil" do it "fails when the association fails validation" do Person.class_eval do validates_associated :name end Name.class_eval do validates_presence_of :first_name end @person.name = Name.new @person.valid?.should be_false @person.errors.on(:name).should_not be_nil end end context "when the associated is nil" do it "returns true" do Person.class_eval do validates_associated :name end @person.valid?.should be_true end end end end describe "#validates_format_of" do it "fails if the field is in the wrong format" do Person.class_eval do validates_format_of :title, :with => /[A-Za-z]/ end @person.title = 10 @person.valid?.should be_false @person.errors.on(:title).should_not be_nil end end describe "#validates_length_of" do it "fails if the field is the wrong length" do Person.class_eval do validates_length_of :title, :minimum => 10 end @person.title = "Testing" @person.valid?.should be_false @person.errors.on(:title).should_not be_nil end end describe "#validates_numericality_of" do it "fails if the field is not a number" do Person.class_eval do validates_numericality_of :age end @person.age = "foo" @person.valid?.should be_false @person.errors.on(:age).should_not be_nil end end describe "#validates_presence_of" do context "on a parent class" do it "fails if the field is nil on the parent" do Person.class_eval do validates_presence_of :title end @person.valid?.should be_false @person.errors.on(:title).should_not be_nil end it "fails if the field is nil on a subclass" do Canvas.class_eval do validates_presence_of :name end @firefox.valid?.should be_false @firefox.errors.on(:name).should_not be_nil end end context "on a subclass" do it "parent class does not get subclass validations" do Firefox.class_eval do validates_presence_of :name end @canvas.valid?.should be_true end end end describe "#validates_true_for" do it "fails if the logic returns false" do Person.class_eval do validates_true_for :title, :logic => lambda { title == "Esquire" } end @person.valid?.should be_false @person.errors.on(:title).should_not be_nil end end end end end