/test/unit/test_embedded_document.rb
Ruby | 682 lines | 565 code | 117 blank | 0 comment | 134 complexity | fcb649f19d423acf00869f59a245cfa8 MD5 | raw file
- require 'test_helper'
- module KeyOverride
- def other_child
- self[:other_child] || "special result"
- end
- def other_child=(value)
- super(value + " modified")
- end
- end
- class EmbeddedDocumentTest < Test::Unit::TestCase
- context "EmbeddedDocuments" do
- setup do
- class ::Grandparent
- include MongoMapper::EmbeddedDocument
- key :grandparent, String
- end
- class ::Parent < ::Grandparent
- include MongoMapper::EmbeddedDocument
- key :parent, String
- end
- class ::Child < ::Parent
- include MongoMapper::EmbeddedDocument
- key :child, String
- end
- class ::OtherChild < ::Parent
- include MongoMapper::EmbeddedDocument
- include KeyOverride
- key :other_child, String
- end
- class ::EDocWithAValidation
- include MongoMapper::EmbeddedDocument
- key :name, String, :required => true
- end
- class ::DocWithAValidation
- include MongoMapper::Document
- key :name, String, :required => true
- many :e_doc_with_a_validations
- end
- end
- teardown do
- Object.send :remove_const, 'Grandparent' if defined?(::Grandparent)
- Object.send :remove_const, 'Parent' if defined?(::Parent)
- Object.send :remove_const, 'Child' if defined?(::Child)
- Object.send :remove_const, 'OtherChild' if defined?(::OtherChild)
- Object.send :remove_const, 'EDocWithAValidation' if defined?(::EDocWithAValidation)
- Object.send :remove_const, 'DocWithAValidation' if defined?(::DocWithAValidation)
- end
- context "Including MongoMapper::EmbeddedDocument in a class" do
- setup do
- @klass = EDoc()
- end
- should "add _id key" do
- @klass.keys['_id'].should_not be_nil
- end
- should "know it is using object id" do
- @klass.using_object_id?.should be_true
- end
- should "know it is not using object id if _id type is changed" do
- @klass.key :_id, String
- @klass.using_object_id?.should be_false
- end
- end
- context "Class Methods" do
- should "include logger" do
- @klass = EDoc()
- @klass.logger.should == MongoMapper.logger
- @klass.logger.should be_instance_of(Logger)
- end
- should "return false for embeddable" do
- EDoc().embeddable?.should be_true
- end
- context "#to_mongo" do
- setup { @klass = EDoc() }
- should "be nil if nil" do
- @klass.to_mongo(nil).should be_nil
- end
- should "convert to_mongo for other values" do
- doc = @klass.new(:foo => 'bar')
- to_mongo = @klass.to_mongo(doc)
- to_mongo.is_a?(Hash).should be_true
- to_mongo['foo'].should == 'bar'
- end
- end
- context "#from_mongo" do
- setup { @klass = EDoc() }
- should "be nil if nil" do
- @klass.from_mongo(nil).should be_nil
- end
- should "be instance if instance of class" do
- doc = @klass.new
- @klass.from_mongo(doc).should == doc
- end
- should "be instance if hash of attributes" do
- doc = @klass.from_mongo({:foo => 'bar'})
- doc.instance_of?(@klass).should be_true
- doc.foo.should == 'bar'
- end
- end
- context "defining a key" do
- setup do
- @document = EDoc()
- end
- should "work with name" do
- key = @document.key(:name)
- key.name.should == 'name'
- end
- should "work with name and type" do
- key = @document.key(:name, String)
- key.name.should == 'name'
- key.type.should == String
- end
- should "work with name, type and options" do
- key = @document.key(:name, String, :required => true)
- key.name.should == 'name'
- key.type.should == String
- key.options[:required].should be_true
- end
- should "work with name and options" do
- key = @document.key(:name, :required => true)
- key.name.should == 'name'
- key.options[:required].should be_true
- end
- should "be tracked per document" do
- @document.key(:name, String)
- @document.key(:age, Integer)
- @document.keys['name'].name.should == 'name'
- @document.keys['name'].type.should == String
- @document.keys['age'].name.should == 'age'
- @document.keys['age'].type.should == Integer
- end
- should "be redefinable" do
- @document.key(:foo, String)
- @document.keys['foo'].type.should == String
- @document.key(:foo, Integer)
- @document.keys['foo'].type.should == Integer
- end
- should "create reader method" do
- @document.new.should_not respond_to(:foo)
- @document.key(:foo, String)
- @document.new.should respond_to(:foo)
- end
- should "create reader before type cast method" do
- @document.new.should_not respond_to(:foo_before_type_cast)
- @document.key(:foo, String)
- @document.new.should respond_to(:foo_before_type_cast)
- end
- should "create writer method" do
- @document.new.should_not respond_to(:foo=)
- @document.key(:foo, String)
- @document.new.should respond_to(:foo=)
- end
- should "create boolean method" do
- @document.new.should_not respond_to(:foo?)
- @document.key(:foo, String)
- @document.new.should respond_to(:foo?)
- end
- end
- context "keys" do
- should "be inherited" do
- Grandparent.keys.keys.sort.should == ['_id', '_type', 'grandparent']
- Parent.keys.keys.sort.should == ['_id', '_type', 'grandparent', 'parent']
- Child.keys.keys.sort.should == ['_id', '_type', 'child', 'grandparent', 'parent']
- end
- should "propogate to descendants if key added after class definition" do
- Grandparent.key :foo, String
- Grandparent.keys.keys.sort.should == ['_id', '_type', 'foo', 'grandparent']
- Parent.keys.keys.sort.should == ['_id', '_type', 'foo', 'grandparent', 'parent']
- Child.keys.keys.sort.should == ['_id', '_type', 'child', 'foo', 'grandparent', 'parent']
- end
- should "not add anonymous objects to the ancestor tree" do
- OtherChild.ancestors.any? { |a| a.name.blank? }.should be_false
- end
- should "not include descendant keys" do
- lambda { Parent.new.other_child }.should raise_error
- end
- end
- context "descendants" do
- should "default to an empty array" do
- Child.descendants.should == []
- end
- should "be recorded" do
- Grandparent.direct_descendants.should == [Parent]
- Grandparent.descendants.to_set.should == [Parent, Child, OtherChild].to_set
- Parent.descendants.should == [Child, OtherChild]
- end
- end
- end
- context "An instance of an embedded document" do
- setup do
- @document = EDoc do
- key :name, String
- key :age, Integer
- end
- end
- should "respond to cache_key" do
- @document.new.should respond_to(:cache_key)
- end
- should "have access to class logger" do
- doc = @document.new
- doc.logger.should == @document.logger
- doc.logger.should be_instance_of(Logger)
- end
- should "automatically have an _id key" do
- @document.keys.keys.should include('_id')
- end
- should "create id during initialization" do
- @document.new._id.should be_instance_of(BSON::ObjectId)
- end
- should "have id method returns _id" do
- id = BSON::ObjectId.new
- doc = @document.new(:_id => id)
- doc.id.should == id
- end
- should "convert string object id to mongo object id when assigning id with _id object id type" do
- id = BSON::ObjectId.new
- doc = @document.new(:id => id.to_s)
- doc._id.should == id
- doc.id.should == id
- doc = @document.new(:_id => id.to_s)
- doc._id.should == id
- doc.id.should == id
- end
- context "_parent_document" do
- should "default to nil" do
- @document.new._parent_document.should be_nil
- @document.new._root_document.should be_nil
- end
- should "set _root_document when setting _parent_document" do
- root = Doc().new
- doc = @document.new(:_parent_document => root)
- doc._parent_document.should be(root)
- doc._root_document.should be(root)
- end
- should "set _root_document when setting _parent_document on embedded many" do
- root = Doc().new
- klass = EDoc { many :children }
- parent = klass.new(:_parent_document => root, :children => [{}])
- child = parent.children.first
- child._parent_document.should be(parent)
- child._root_document.should be(root)
- end
- end
- context "being initialized" do
- should "accept a hash that sets keys and values" do
- doc = @document.new(:name => 'John', :age => 23)
- doc.attributes.keys.sort.should == ['_id', 'age', 'name']
- doc.attributes['name'].should == 'John'
- doc.attributes['age'].should == 23
- end
- should "be able to assign keys dynamically" do
- doc = @document.new(:name => 'John', :skills => ['ruby', 'rails'])
- doc.name.should == 'John'
- doc.skills.should == ['ruby', 'rails']
- end
- should "not throw error if initialized with nil" do
- assert_nothing_raised { @document.new(nil) }
- end
- end
- context "initialized when _type key present" do
- setup do
- @klass = EDoc('FooBar') { key :_type, String }
- end
- should "set _type to class name" do
- @klass.new._type.should == 'FooBar'
- end
- should "ignore _type attribute and always use class" do
- @klass.new(:_type => 'Foo')._type.should == 'FooBar'
- end
- end
- context "attributes=" do
- should "update values for keys provided" do
- doc = @document.new(:name => 'foobar', :age => 10)
- doc.attributes = {:name => 'new value', :age => 5}
- doc.attributes[:name].should == 'new value'
- doc.attributes[:age].should == 5
- end
- should "not update values for keys that were not provided" do
- doc = @document.new(:name => 'foobar', :age => 10)
- doc.attributes = {:name => 'new value'}
- doc.attributes[:name].should == 'new value'
- doc.attributes[:age].should == 10
- end
- should "work with pre-defined methods" do
- @document.class_eval do
- attr_writer :password
- def passwd
- @password
- end
- end
- doc = @document.new(:name => 'foobar', :password => 'secret')
- doc.passwd.should == 'secret'
- end
- should "type cast key values" do
- doc = @document.new(:name => 1234, :age => '21')
- doc.name.should == '1234'
- doc.age.should == 21
- end
- end
- context "attributes" do
- should "default to hash with all keys" do
- doc = @document.new
- doc.keys.keys.sort.should == ['_id', 'age', 'name']
- doc.attributes.keys.sort.should == ['_id']
- end
- should "return all keys with values" do
- doc = @document.new(:name => 'string', :age => nil)
- doc.attributes.keys.sort.should == ['_id', 'name']
- doc.keys.keys.sort.should == ['_id', 'age', 'name']
- doc.attributes.values.should include('string')
- doc.attributes.values.should_not include(nil)
- end
- should "have indifferent access" do
- doc = @document.new(:name => 'string')
- doc.attributes[:name].should == 'string'
- doc.attributes['name'].should == 'string'
- end
- end
- context "to_mongo" do
- should "default to hash with _id key" do
- doc = @document.new
- doc.to_mongo.keys.sort.should == ['_id']
- doc.keys.keys.sort.should == ['_id', 'age', 'name']
- end
- should "return all keys" do
- doc = @document.new(:name => 'string', :age => nil)
- doc.keys.keys.sort.should == ['_id', 'age', 'name']
- doc.to_mongo.keys.sort.should == ['_id','name']
- doc.to_mongo.values.should include('string')
- doc.to_mongo.values.should_not include(nil)
- end
- end
- context "key shorcut access" do
- context "[]" do
- should "work when key found" do
- doc = @document.new(:name => 'string')
- doc[:name].should == 'string'
- end
- should "return nil when not found" do
- doc = @document.new(:name => 'string')
- doc[:not_here].should be_nil
- end
- end
- context "[]=" do
- should "write key value for existing key" do
- doc = @document.new
- doc[:name] = 'string'
- doc[:name].should == 'string'
- end
- should "create key and write value for missing key" do
- doc = @document.new
- doc[:foo] = 'string'
- doc.class.keys.include?('foo').should be_true
- doc[:foo].should == 'string'
- end
- should "share the new key with the class" do
- doc = @document.new
- doc[:foo] = 'string'
- @document.keys.should include('foo')
- end
- end
- end
- context "reading a key" do
- should "work for defined keys" do
- doc = @document.new(:name => 'string')
- doc.name.should == 'string'
- end
- should "raise no method error for undefined keys" do
- doc = @document.new
- lambda { doc.fart }.should raise_error(NoMethodError)
- end
- should "be accessible for use in the model" do
- @document.class_eval do
- def name_and_age
- "#{self[:name]} (#{self[:age]})"
- end
- end
- doc = @document.new(:name => 'John', :age => 27)
- doc.name_and_age.should == 'John (27)'
- end
- should "set instance variable" do
- @document.key :foo, Array
- doc = @document.new
- doc.instance_variable_get("@foo").should be_nil
- doc.foo
- doc.instance_variable_get("@foo").should == []
- end
- should "be overrideable by modules" do
- @document = Doc do
- key :other_child, String
- end
- child = @document.new
- child.other_child.should be_nil
- @document.send :include, KeyOverride
- overriden_child = @document.new
- overriden_child.other_child.should == 'special result'
- end
- end
- context "reading a key before typcasting" do
- should "work for defined keys" do
- doc = @document.new(:name => 12)
- doc.name_before_type_cast.should == 12
- end
- should "raise no method error for undefined keys" do
- doc = @document.new
- lambda { doc.foo_before_type_cast }.should raise_error(NoMethodError)
- end
- should "be accessible for use in a document" do
- @document.class_eval do
- def untypcasted_name
- read_key_before_type_cast(:name)
- end
- end
- doc = @document.new(:name => 12)
- doc.name.should == '12'
- doc.untypcasted_name.should == 12
- end
- end
- context "writing a key" do
- should "work for defined keys" do
- doc = @document.new
- doc.name = 'John'
- doc.name.should == 'John'
- end
- should "raise no method error for undefined keys" do
- doc = @document.new
- lambda { doc.fart = 'poof!' }.should raise_error(NoMethodError)
- end
- should "type cast value" do
- doc = @document.new
- doc.name = 1234
- doc.name.should == '1234'
- doc.age = '21'
- doc.age.should == 21
- end
- should "be accessible for use in the model" do
- @document.class_eval do
- def name_and_age=(new_value)
- new_value.match(/([^\(\s]+) \((.*)\)/)
- write_key :name, $1
- write_key :age, $2
- end
- end
- doc = @document.new
- doc.name_and_age = 'Frank (62)'
- doc.name.should == 'Frank'
- doc.age.should == 62
- end
- should "be overrideable by modules" do
- @document = Doc do
- key :other_child, String
- end
- child = @document.new(:other_child => 'foo')
- child.other_child.should == 'foo'
- @document.send :include, KeyOverride
- overriden_child = @document.new(:other_child => 'foo')
- overriden_child.other_child.should == 'foo modified'
- end
- end # writing a key
- context "checking if a keys value is present" do
- should "work for defined keys" do
- doc = @document.new
- doc.name?.should be_false
- doc.name = 'John'
- doc.name?.should be_true
- end
- should "raise no method error for undefined keys" do
- doc = @document.new
- lambda { doc.fart? }.should raise_error(NoMethodError)
- end
- end
- context "equality" do
- setup do
- @oid = BSON::ObjectId.new
- end
- should "delegate hash to _id" do
- doc = @document.new
- doc.hash.should == doc._id.hash
- end
- should "delegate eql to ==" do
- doc = @document.new
- other = @document.new
- doc.eql?(other).should == (doc == other)
- doc.eql?(doc).should == (doc == doc)
- end
- should "know if same object as another" do
- doc = @document.new
- doc.should equal(doc)
- doc.should_not equal(@document.new)
- end
- should "allow set operations on array of documents" do
- doc = @document.new
- ([doc] & [doc]).should == [doc]
- end
- should "be equal if id and class are the same" do
- (@document.new('_id' => @oid) == @document.new('_id' => @oid)).should be_true
- end
- should "not be equal if class same but id different" do
- (@document.new('_id' => @oid) == @document.new('_id' => BSON::ObjectId.new)).should be_false
- end
- should "not be equal if id same but class different" do
- another_document = Doc()
- (@document.new('_id' => @oid) == another_document.new('_id' => @oid)).should be_false
- end
- end
- context "reading keys with default values" do
- setup do
- @document = EDoc do
- key :name, String, :default => 'foo'
- key :age, Integer, :default => 20
- key :net_worth, Float, :default => 100.00
- key :active, Boolean, :default => true
- key :smart, Boolean, :default => false
- key :skills, Array, :default => [1]
- key :options, Hash, :default => {'foo' => 'bar'}
- end
- @doc = @document.new
- end
- should "work for strings" do
- @doc.name.should == 'foo'
- end
- should "work for integers" do
- @doc.age.should == 20
- end
- should "work for floats" do
- @doc.net_worth.should == 100.00
- end
- should "work for booleans" do
- @doc.active.should == true
- @doc.smart.should == false
- end
- should "work for arrays" do
- @doc.skills.should == [1]
- @doc.skills << 2
- @doc.skills.should == [1, 2]
- end
- should "work for hashes" do
- @doc.options['foo'].should == 'bar'
- @doc.options['baz'] = 'wick'
- @doc.options['baz'].should == 'wick'
- end
- end
- context "#save!" do
- setup do
- @root = DocWithAValidation.create(:name => "Root")
- @doc = @root.e_doc_with_a_validations.build :name => "Embedded"
- end
- should "should save when valid" do
- @doc.save!
- @root.reload.e_doc_with_a_validations.first.should == @doc
- end
- should "should raise errors when invalid" do
- @doc.name = ''
- lambda{ @doc.save! }.should raise_error(MongoMapper::DocumentNotValid, "Validation failed: Name can't be empty")
- end
- should "should raise errors when root document is invalid" do
- @root.name = ''
- @root.save(:validate => false)
- lambda{ @doc.save! }.should raise_error(MongoMapper::DocumentNotValid, "Foo")
- end
- end
- end # instance of a embedded document
- end
- end