PageRenderTime 48ms CodeModel.GetById 15ms RepoModel.GetById 1ms app.codeStats 0ms

/test/unit/test_embedded_document.rb

http://github.com/jnunemaker/mongomapper
Ruby | 682 lines | 565 code | 117 blank | 0 comment | 134 complexity | fcb649f19d423acf00869f59a245cfa8 MD5 | raw file
  1. require 'test_helper'
  2. module KeyOverride
  3. def other_child
  4. self[:other_child] || "special result"
  5. end
  6. def other_child=(value)
  7. super(value + " modified")
  8. end
  9. end
  10. class EmbeddedDocumentTest < Test::Unit::TestCase
  11. context "EmbeddedDocuments" do
  12. setup do
  13. class ::Grandparent
  14. include MongoMapper::EmbeddedDocument
  15. key :grandparent, String
  16. end
  17. class ::Parent < ::Grandparent
  18. include MongoMapper::EmbeddedDocument
  19. key :parent, String
  20. end
  21. class ::Child < ::Parent
  22. include MongoMapper::EmbeddedDocument
  23. key :child, String
  24. end
  25. class ::OtherChild < ::Parent
  26. include MongoMapper::EmbeddedDocument
  27. include KeyOverride
  28. key :other_child, String
  29. end
  30. class ::EDocWithAValidation
  31. include MongoMapper::EmbeddedDocument
  32. key :name, String, :required => true
  33. end
  34. class ::DocWithAValidation
  35. include MongoMapper::Document
  36. key :name, String, :required => true
  37. many :e_doc_with_a_validations
  38. end
  39. end
  40. teardown do
  41. Object.send :remove_const, 'Grandparent' if defined?(::Grandparent)
  42. Object.send :remove_const, 'Parent' if defined?(::Parent)
  43. Object.send :remove_const, 'Child' if defined?(::Child)
  44. Object.send :remove_const, 'OtherChild' if defined?(::OtherChild)
  45. Object.send :remove_const, 'EDocWithAValidation' if defined?(::EDocWithAValidation)
  46. Object.send :remove_const, 'DocWithAValidation' if defined?(::DocWithAValidation)
  47. end
  48. context "Including MongoMapper::EmbeddedDocument in a class" do
  49. setup do
  50. @klass = EDoc()
  51. end
  52. should "add _id key" do
  53. @klass.keys['_id'].should_not be_nil
  54. end
  55. should "know it is using object id" do
  56. @klass.using_object_id?.should be_true
  57. end
  58. should "know it is not using object id if _id type is changed" do
  59. @klass.key :_id, String
  60. @klass.using_object_id?.should be_false
  61. end
  62. end
  63. context "Class Methods" do
  64. should "include logger" do
  65. @klass = EDoc()
  66. @klass.logger.should == MongoMapper.logger
  67. @klass.logger.should be_instance_of(Logger)
  68. end
  69. should "return false for embeddable" do
  70. EDoc().embeddable?.should be_true
  71. end
  72. context "#to_mongo" do
  73. setup { @klass = EDoc() }
  74. should "be nil if nil" do
  75. @klass.to_mongo(nil).should be_nil
  76. end
  77. should "convert to_mongo for other values" do
  78. doc = @klass.new(:foo => 'bar')
  79. to_mongo = @klass.to_mongo(doc)
  80. to_mongo.is_a?(Hash).should be_true
  81. to_mongo['foo'].should == 'bar'
  82. end
  83. end
  84. context "#from_mongo" do
  85. setup { @klass = EDoc() }
  86. should "be nil if nil" do
  87. @klass.from_mongo(nil).should be_nil
  88. end
  89. should "be instance if instance of class" do
  90. doc = @klass.new
  91. @klass.from_mongo(doc).should == doc
  92. end
  93. should "be instance if hash of attributes" do
  94. doc = @klass.from_mongo({:foo => 'bar'})
  95. doc.instance_of?(@klass).should be_true
  96. doc.foo.should == 'bar'
  97. end
  98. end
  99. context "defining a key" do
  100. setup do
  101. @document = EDoc()
  102. end
  103. should "work with name" do
  104. key = @document.key(:name)
  105. key.name.should == 'name'
  106. end
  107. should "work with name and type" do
  108. key = @document.key(:name, String)
  109. key.name.should == 'name'
  110. key.type.should == String
  111. end
  112. should "work with name, type and options" do
  113. key = @document.key(:name, String, :required => true)
  114. key.name.should == 'name'
  115. key.type.should == String
  116. key.options[:required].should be_true
  117. end
  118. should "work with name and options" do
  119. key = @document.key(:name, :required => true)
  120. key.name.should == 'name'
  121. key.options[:required].should be_true
  122. end
  123. should "be tracked per document" do
  124. @document.key(:name, String)
  125. @document.key(:age, Integer)
  126. @document.keys['name'].name.should == 'name'
  127. @document.keys['name'].type.should == String
  128. @document.keys['age'].name.should == 'age'
  129. @document.keys['age'].type.should == Integer
  130. end
  131. should "be redefinable" do
  132. @document.key(:foo, String)
  133. @document.keys['foo'].type.should == String
  134. @document.key(:foo, Integer)
  135. @document.keys['foo'].type.should == Integer
  136. end
  137. should "create reader method" do
  138. @document.new.should_not respond_to(:foo)
  139. @document.key(:foo, String)
  140. @document.new.should respond_to(:foo)
  141. end
  142. should "create reader before type cast method" do
  143. @document.new.should_not respond_to(:foo_before_type_cast)
  144. @document.key(:foo, String)
  145. @document.new.should respond_to(:foo_before_type_cast)
  146. end
  147. should "create writer method" do
  148. @document.new.should_not respond_to(:foo=)
  149. @document.key(:foo, String)
  150. @document.new.should respond_to(:foo=)
  151. end
  152. should "create boolean method" do
  153. @document.new.should_not respond_to(:foo?)
  154. @document.key(:foo, String)
  155. @document.new.should respond_to(:foo?)
  156. end
  157. end
  158. context "keys" do
  159. should "be inherited" do
  160. Grandparent.keys.keys.sort.should == ['_id', '_type', 'grandparent']
  161. Parent.keys.keys.sort.should == ['_id', '_type', 'grandparent', 'parent']
  162. Child.keys.keys.sort.should == ['_id', '_type', 'child', 'grandparent', 'parent']
  163. end
  164. should "propogate to descendants if key added after class definition" do
  165. Grandparent.key :foo, String
  166. Grandparent.keys.keys.sort.should == ['_id', '_type', 'foo', 'grandparent']
  167. Parent.keys.keys.sort.should == ['_id', '_type', 'foo', 'grandparent', 'parent']
  168. Child.keys.keys.sort.should == ['_id', '_type', 'child', 'foo', 'grandparent', 'parent']
  169. end
  170. should "not add anonymous objects to the ancestor tree" do
  171. OtherChild.ancestors.any? { |a| a.name.blank? }.should be_false
  172. end
  173. should "not include descendant keys" do
  174. lambda { Parent.new.other_child }.should raise_error
  175. end
  176. end
  177. context "descendants" do
  178. should "default to an empty array" do
  179. Child.descendants.should == []
  180. end
  181. should "be recorded" do
  182. Grandparent.direct_descendants.should == [Parent]
  183. Grandparent.descendants.to_set.should == [Parent, Child, OtherChild].to_set
  184. Parent.descendants.should == [Child, OtherChild]
  185. end
  186. end
  187. end
  188. context "An instance of an embedded document" do
  189. setup do
  190. @document = EDoc do
  191. key :name, String
  192. key :age, Integer
  193. end
  194. end
  195. should "respond to cache_key" do
  196. @document.new.should respond_to(:cache_key)
  197. end
  198. should "have access to class logger" do
  199. doc = @document.new
  200. doc.logger.should == @document.logger
  201. doc.logger.should be_instance_of(Logger)
  202. end
  203. should "automatically have an _id key" do
  204. @document.keys.keys.should include('_id')
  205. end
  206. should "create id during initialization" do
  207. @document.new._id.should be_instance_of(BSON::ObjectId)
  208. end
  209. should "have id method returns _id" do
  210. id = BSON::ObjectId.new
  211. doc = @document.new(:_id => id)
  212. doc.id.should == id
  213. end
  214. should "convert string object id to mongo object id when assigning id with _id object id type" do
  215. id = BSON::ObjectId.new
  216. doc = @document.new(:id => id.to_s)
  217. doc._id.should == id
  218. doc.id.should == id
  219. doc = @document.new(:_id => id.to_s)
  220. doc._id.should == id
  221. doc.id.should == id
  222. end
  223. context "_parent_document" do
  224. should "default to nil" do
  225. @document.new._parent_document.should be_nil
  226. @document.new._root_document.should be_nil
  227. end
  228. should "set _root_document when setting _parent_document" do
  229. root = Doc().new
  230. doc = @document.new(:_parent_document => root)
  231. doc._parent_document.should be(root)
  232. doc._root_document.should be(root)
  233. end
  234. should "set _root_document when setting _parent_document on embedded many" do
  235. root = Doc().new
  236. klass = EDoc { many :children }
  237. parent = klass.new(:_parent_document => root, :children => [{}])
  238. child = parent.children.first
  239. child._parent_document.should be(parent)
  240. child._root_document.should be(root)
  241. end
  242. end
  243. context "being initialized" do
  244. should "accept a hash that sets keys and values" do
  245. doc = @document.new(:name => 'John', :age => 23)
  246. doc.attributes.keys.sort.should == ['_id', 'age', 'name']
  247. doc.attributes['name'].should == 'John'
  248. doc.attributes['age'].should == 23
  249. end
  250. should "be able to assign keys dynamically" do
  251. doc = @document.new(:name => 'John', :skills => ['ruby', 'rails'])
  252. doc.name.should == 'John'
  253. doc.skills.should == ['ruby', 'rails']
  254. end
  255. should "not throw error if initialized with nil" do
  256. assert_nothing_raised { @document.new(nil) }
  257. end
  258. end
  259. context "initialized when _type key present" do
  260. setup do
  261. @klass = EDoc('FooBar') { key :_type, String }
  262. end
  263. should "set _type to class name" do
  264. @klass.new._type.should == 'FooBar'
  265. end
  266. should "ignore _type attribute and always use class" do
  267. @klass.new(:_type => 'Foo')._type.should == 'FooBar'
  268. end
  269. end
  270. context "attributes=" do
  271. should "update values for keys provided" do
  272. doc = @document.new(:name => 'foobar', :age => 10)
  273. doc.attributes = {:name => 'new value', :age => 5}
  274. doc.attributes[:name].should == 'new value'
  275. doc.attributes[:age].should == 5
  276. end
  277. should "not update values for keys that were not provided" do
  278. doc = @document.new(:name => 'foobar', :age => 10)
  279. doc.attributes = {:name => 'new value'}
  280. doc.attributes[:name].should == 'new value'
  281. doc.attributes[:age].should == 10
  282. end
  283. should "work with pre-defined methods" do
  284. @document.class_eval do
  285. attr_writer :password
  286. def passwd
  287. @password
  288. end
  289. end
  290. doc = @document.new(:name => 'foobar', :password => 'secret')
  291. doc.passwd.should == 'secret'
  292. end
  293. should "type cast key values" do
  294. doc = @document.new(:name => 1234, :age => '21')
  295. doc.name.should == '1234'
  296. doc.age.should == 21
  297. end
  298. end
  299. context "attributes" do
  300. should "default to hash with all keys" do
  301. doc = @document.new
  302. doc.keys.keys.sort.should == ['_id', 'age', 'name']
  303. doc.attributes.keys.sort.should == ['_id']
  304. end
  305. should "return all keys with values" do
  306. doc = @document.new(:name => 'string', :age => nil)
  307. doc.attributes.keys.sort.should == ['_id', 'name']
  308. doc.keys.keys.sort.should == ['_id', 'age', 'name']
  309. doc.attributes.values.should include('string')
  310. doc.attributes.values.should_not include(nil)
  311. end
  312. should "have indifferent access" do
  313. doc = @document.new(:name => 'string')
  314. doc.attributes[:name].should == 'string'
  315. doc.attributes['name'].should == 'string'
  316. end
  317. end
  318. context "to_mongo" do
  319. should "default to hash with _id key" do
  320. doc = @document.new
  321. doc.to_mongo.keys.sort.should == ['_id']
  322. doc.keys.keys.sort.should == ['_id', 'age', 'name']
  323. end
  324. should "return all keys" do
  325. doc = @document.new(:name => 'string', :age => nil)
  326. doc.keys.keys.sort.should == ['_id', 'age', 'name']
  327. doc.to_mongo.keys.sort.should == ['_id','name']
  328. doc.to_mongo.values.should include('string')
  329. doc.to_mongo.values.should_not include(nil)
  330. end
  331. end
  332. context "key shorcut access" do
  333. context "[]" do
  334. should "work when key found" do
  335. doc = @document.new(:name => 'string')
  336. doc[:name].should == 'string'
  337. end
  338. should "return nil when not found" do
  339. doc = @document.new(:name => 'string')
  340. doc[:not_here].should be_nil
  341. end
  342. end
  343. context "[]=" do
  344. should "write key value for existing key" do
  345. doc = @document.new
  346. doc[:name] = 'string'
  347. doc[:name].should == 'string'
  348. end
  349. should "create key and write value for missing key" do
  350. doc = @document.new
  351. doc[:foo] = 'string'
  352. doc.class.keys.include?('foo').should be_true
  353. doc[:foo].should == 'string'
  354. end
  355. should "share the new key with the class" do
  356. doc = @document.new
  357. doc[:foo] = 'string'
  358. @document.keys.should include('foo')
  359. end
  360. end
  361. end
  362. context "reading a key" do
  363. should "work for defined keys" do
  364. doc = @document.new(:name => 'string')
  365. doc.name.should == 'string'
  366. end
  367. should "raise no method error for undefined keys" do
  368. doc = @document.new
  369. lambda { doc.fart }.should raise_error(NoMethodError)
  370. end
  371. should "be accessible for use in the model" do
  372. @document.class_eval do
  373. def name_and_age
  374. "#{self[:name]} (#{self[:age]})"
  375. end
  376. end
  377. doc = @document.new(:name => 'John', :age => 27)
  378. doc.name_and_age.should == 'John (27)'
  379. end
  380. should "set instance variable" do
  381. @document.key :foo, Array
  382. doc = @document.new
  383. doc.instance_variable_get("@foo").should be_nil
  384. doc.foo
  385. doc.instance_variable_get("@foo").should == []
  386. end
  387. should "be overrideable by modules" do
  388. @document = Doc do
  389. key :other_child, String
  390. end
  391. child = @document.new
  392. child.other_child.should be_nil
  393. @document.send :include, KeyOverride
  394. overriden_child = @document.new
  395. overriden_child.other_child.should == 'special result'
  396. end
  397. end
  398. context "reading a key before typcasting" do
  399. should "work for defined keys" do
  400. doc = @document.new(:name => 12)
  401. doc.name_before_type_cast.should == 12
  402. end
  403. should "raise no method error for undefined keys" do
  404. doc = @document.new
  405. lambda { doc.foo_before_type_cast }.should raise_error(NoMethodError)
  406. end
  407. should "be accessible for use in a document" do
  408. @document.class_eval do
  409. def untypcasted_name
  410. read_key_before_type_cast(:name)
  411. end
  412. end
  413. doc = @document.new(:name => 12)
  414. doc.name.should == '12'
  415. doc.untypcasted_name.should == 12
  416. end
  417. end
  418. context "writing a key" do
  419. should "work for defined keys" do
  420. doc = @document.new
  421. doc.name = 'John'
  422. doc.name.should == 'John'
  423. end
  424. should "raise no method error for undefined keys" do
  425. doc = @document.new
  426. lambda { doc.fart = 'poof!' }.should raise_error(NoMethodError)
  427. end
  428. should "type cast value" do
  429. doc = @document.new
  430. doc.name = 1234
  431. doc.name.should == '1234'
  432. doc.age = '21'
  433. doc.age.should == 21
  434. end
  435. should "be accessible for use in the model" do
  436. @document.class_eval do
  437. def name_and_age=(new_value)
  438. new_value.match(/([^\(\s]+) \((.*)\)/)
  439. write_key :name, $1
  440. write_key :age, $2
  441. end
  442. end
  443. doc = @document.new
  444. doc.name_and_age = 'Frank (62)'
  445. doc.name.should == 'Frank'
  446. doc.age.should == 62
  447. end
  448. should "be overrideable by modules" do
  449. @document = Doc do
  450. key :other_child, String
  451. end
  452. child = @document.new(:other_child => 'foo')
  453. child.other_child.should == 'foo'
  454. @document.send :include, KeyOverride
  455. overriden_child = @document.new(:other_child => 'foo')
  456. overriden_child.other_child.should == 'foo modified'
  457. end
  458. end # writing a key
  459. context "checking if a keys value is present" do
  460. should "work for defined keys" do
  461. doc = @document.new
  462. doc.name?.should be_false
  463. doc.name = 'John'
  464. doc.name?.should be_true
  465. end
  466. should "raise no method error for undefined keys" do
  467. doc = @document.new
  468. lambda { doc.fart? }.should raise_error(NoMethodError)
  469. end
  470. end
  471. context "equality" do
  472. setup do
  473. @oid = BSON::ObjectId.new
  474. end
  475. should "delegate hash to _id" do
  476. doc = @document.new
  477. doc.hash.should == doc._id.hash
  478. end
  479. should "delegate eql to ==" do
  480. doc = @document.new
  481. other = @document.new
  482. doc.eql?(other).should == (doc == other)
  483. doc.eql?(doc).should == (doc == doc)
  484. end
  485. should "know if same object as another" do
  486. doc = @document.new
  487. doc.should equal(doc)
  488. doc.should_not equal(@document.new)
  489. end
  490. should "allow set operations on array of documents" do
  491. doc = @document.new
  492. ([doc] & [doc]).should == [doc]
  493. end
  494. should "be equal if id and class are the same" do
  495. (@document.new('_id' => @oid) == @document.new('_id' => @oid)).should be_true
  496. end
  497. should "not be equal if class same but id different" do
  498. (@document.new('_id' => @oid) == @document.new('_id' => BSON::ObjectId.new)).should be_false
  499. end
  500. should "not be equal if id same but class different" do
  501. another_document = Doc()
  502. (@document.new('_id' => @oid) == another_document.new('_id' => @oid)).should be_false
  503. end
  504. end
  505. context "reading keys with default values" do
  506. setup do
  507. @document = EDoc do
  508. key :name, String, :default => 'foo'
  509. key :age, Integer, :default => 20
  510. key :net_worth, Float, :default => 100.00
  511. key :active, Boolean, :default => true
  512. key :smart, Boolean, :default => false
  513. key :skills, Array, :default => [1]
  514. key :options, Hash, :default => {'foo' => 'bar'}
  515. end
  516. @doc = @document.new
  517. end
  518. should "work for strings" do
  519. @doc.name.should == 'foo'
  520. end
  521. should "work for integers" do
  522. @doc.age.should == 20
  523. end
  524. should "work for floats" do
  525. @doc.net_worth.should == 100.00
  526. end
  527. should "work for booleans" do
  528. @doc.active.should == true
  529. @doc.smart.should == false
  530. end
  531. should "work for arrays" do
  532. @doc.skills.should == [1]
  533. @doc.skills << 2
  534. @doc.skills.should == [1, 2]
  535. end
  536. should "work for hashes" do
  537. @doc.options['foo'].should == 'bar'
  538. @doc.options['baz'] = 'wick'
  539. @doc.options['baz'].should == 'wick'
  540. end
  541. end
  542. context "#save!" do
  543. setup do
  544. @root = DocWithAValidation.create(:name => "Root")
  545. @doc = @root.e_doc_with_a_validations.build :name => "Embedded"
  546. end
  547. should "should save when valid" do
  548. @doc.save!
  549. @root.reload.e_doc_with_a_validations.first.should == @doc
  550. end
  551. should "should raise errors when invalid" do
  552. @doc.name = ''
  553. lambda{ @doc.save! }.should raise_error(MongoMapper::DocumentNotValid, "Validation failed: Name can't be empty")
  554. end
  555. should "should raise errors when root document is invalid" do
  556. @root.name = ''
  557. @root.save(:validate => false)
  558. lambda{ @doc.save! }.should raise_error(MongoMapper::DocumentNotValid, "Foo")
  559. end
  560. end
  561. end # instance of a embedded document
  562. end
  563. end