PageRenderTime 120ms CodeModel.GetById 32ms RepoModel.GetById 4ms app.codeStats 0ms

/test/functional/test_identity_map.rb

http://github.com/jnunemaker/mongomapper
Ruby | 513 lines | 412 code | 94 blank | 7 comment | 21 complexity | ecef909dc08f4fb06bbdfa152cba25b8 MD5 | raw file
  1. require 'test_helper'
  2. class IdentityMapTest < Test::Unit::TestCase
  3. def assert_in_map(*resources)
  4. [resources].flatten.each do |resource|
  5. resource.identity_map.keys.should include(resource._id)
  6. mapped_resource = resource.identity_map[resource._id]
  7. resource.should equal(mapped_resource)
  8. end
  9. end
  10. def assert_not_in_map(*resources)
  11. [resources].flatten.each do |resource|
  12. resource.identity_map.keys.should_not include(resource._id)
  13. end
  14. end
  15. def expect_no_queries
  16. Mongo::Collection.any_instance.expects(:find_one).never
  17. Mongo::Collection.any_instance.expects(:find).never
  18. end
  19. def expects_one_query
  20. Mongo::Collection.any_instance.expects(:find_one).once.returns({})
  21. end
  22. context "Document" do
  23. setup do
  24. MongoMapper::Plugins::IdentityMap.models.clear
  25. @person_class = Doc('Person') do
  26. plugin MongoMapper::Plugins::IdentityMap
  27. key :name, String
  28. end
  29. @post_class = Doc('Post') do
  30. plugin MongoMapper::Plugins::IdentityMap
  31. key :title, String
  32. key :person_id, ObjectId
  33. end
  34. @post_class.belongs_to :person, :class => @person_class
  35. @person_class.many :posts, :class => @post_class
  36. @post_class.identity_map_on
  37. @person_class.identity_map_on
  38. MongoMapper::Plugins::IdentityMap.clear
  39. end
  40. should "track identity mapped models" do
  41. MongoMapper::Plugins::IdentityMap.models.should == [@person_class, @post_class].to_set
  42. end
  43. should "be able to clear the map of all models" do
  44. person = @person_class.create(:name => 'John')
  45. post = @post_class.create(:title => 'IM 4eva')
  46. assert_in_map(person, post)
  47. MongoMapper::Plugins::IdentityMap.clear
  48. assert_not_in_map(person, post)
  49. [@person_class, @post_class].each { |klass| klass.identity_map.should == {} }
  50. end
  51. context "IM on off status" do
  52. teardown do
  53. @post_class.identity_map_on
  54. @person_class.identity_map_on
  55. end
  56. should "default identity map status to on" do
  57. Doc { plugin MongoMapper::Plugins::IdentityMap }.identity_map_status.should be_true
  58. end
  59. should "be true if on" do
  60. @post_class.identity_map_on
  61. @post_class.should be_identity_map_on
  62. @post_class.should_not be_identity_map_off
  63. end
  64. should "be false if off" do
  65. @post_class.identity_map_off
  66. @post_class.should be_identity_map_off
  67. @post_class.should_not be_identity_map_on
  68. end
  69. should "not share with other classes" do
  70. @post_class.identity_map_off
  71. @person_class.identity_map_on
  72. @post_class.identity_map_status.should_not == @person_class.identity_map_status
  73. end
  74. end
  75. should "default identity map to hash" do
  76. Doc { plugin MongoMapper::Plugins::IdentityMap }.identity_map.should == {}
  77. end
  78. should "add key to map when saved" do
  79. person = @person_class.new
  80. assert_not_in_map(person)
  81. person.save.should be_true
  82. assert_in_map(person)
  83. end
  84. should "allow saving with options" do
  85. person = @person_class.new
  86. assert_nothing_raised do
  87. person.save(:validate => false).should be_true
  88. end
  89. end
  90. should "remove key from map when deleted" do
  91. person = @person_class.create(:name => 'Fred')
  92. assert_in_map(person)
  93. person.destroy
  94. assert_not_in_map(person)
  95. end
  96. context "reload" do
  97. setup do
  98. @person = @person_class.create(:name => 'Fred')
  99. end
  100. should "remove object from identity and re-query" do
  101. assert_in_map(@person)
  102. expects_one_query
  103. @person.reload
  104. end
  105. should "add object back into map" do
  106. assert_in_map(@person)
  107. before_reload = @person
  108. @person.reload.should equal(before_reload)
  109. assert_in_map(@person)
  110. end
  111. end
  112. context "#load" do
  113. setup do
  114. @id = BSON::ObjectId.new
  115. end
  116. should "add document to map" do
  117. loaded = @person_class.load({'_id' => @id, 'name' => 'Frank'})
  118. assert_in_map(loaded)
  119. end
  120. should "return document if already in map" do
  121. first_load = @person_class.load({'_id' => @id, 'name' => 'Frank'})
  122. @person_class.identity_map.expects(:[]=).never
  123. second_load = @person_class.load({'_id' => @id, 'name' => 'Frank'})
  124. first_load.should equal(second_load)
  125. end
  126. end
  127. context "#find (with one id)" do
  128. context "for object not in map" do
  129. setup do
  130. @person = @person_class.create(:name => 'Fred')
  131. @person_class.identity_map.clear
  132. end
  133. should "query the database" do
  134. expects_one_query
  135. @person_class.find(@person.id)
  136. end
  137. should "add object to map" do
  138. assert_not_in_map(@person)
  139. found_person = @person_class.find(@person.id)
  140. assert_in_map(found_person)
  141. end
  142. should "return nil if not found " do
  143. @person_class.find(1234).should be_nil
  144. end
  145. end
  146. context "for object in map" do
  147. setup do
  148. @person = @person_class.create(:name => 'Fred')
  149. end
  150. should "not query database" do
  151. expect_no_queries
  152. @person_class.find(@person.id)
  153. end
  154. should "return exact object" do
  155. assert_in_map(@person)
  156. found_person = @person_class.find(@person.id)
  157. found_person.should equal(@person)
  158. end
  159. end
  160. end
  161. context "#find (with one id and options)" do
  162. setup do
  163. @person = @person_class.create(:name => 'Fred')
  164. @post1 = @person.posts.create(:title => 'I Love Mongo')
  165. @post2 = @person.posts.create(:title => 'Migrations Suck!')
  166. end
  167. # There are times when even though the id matches, other criteria doesn't
  168. # so we need to do the query to ensure that when criteria doesn't match
  169. # the document is in fact not found.
  170. #
  171. # I'm open to not making this query if someone can figure out reliable
  172. # way to check if document matches criteria without querying.
  173. should "query the database" do
  174. assert_in_map(@post1)
  175. expects_one_query
  176. @person.posts.find(@post1.id)
  177. end
  178. should "return exact object" do
  179. assert_in_map(@post1)
  180. @person.posts.find(@post1.id)
  181. assert_in_map(@post1)
  182. end
  183. should "return nil if not found " do
  184. @person.posts.find(1234).should be_nil
  185. end
  186. end
  187. context "#find (with multiple ids)" do
  188. should "add all documents to map" do
  189. person1 = @person_class.create(:name => 'Fred')
  190. person2 = @person_class.create(:name => 'Bill')
  191. person3 = @person_class.create(:name => 'Jesse')
  192. @person_class.identity_map.clear
  193. people = @person_class.find(person1.id, person2.id, person3.id)
  194. assert_in_map(people)
  195. end
  196. should "add missing documents to map and return existing ones" do
  197. person1 = @person_class.create(:name => 'Fred')
  198. @person_class.identity_map.clear
  199. person2 = @person_class.create(:name => 'Bill')
  200. person3 = @person_class.create(:name => 'Jesse')
  201. assert_not_in_map(person1)
  202. assert_in_map(person2, person3)
  203. people = @person_class.find(person1.id, person2.id, person3.id)
  204. assert_in_map(people.first) # making sure one that wasn't mapped now is
  205. assert_in_map(person2, person3)
  206. end
  207. end
  208. context "#first" do
  209. context "for object not in map" do
  210. setup do
  211. @person = @person_class.create(:name => 'Fred')
  212. @person_class.identity_map.clear
  213. end
  214. should "query the database" do
  215. expects_one_query
  216. @person_class.first(:_id => @person.id)
  217. end
  218. should "add object to map" do
  219. assert_not_in_map(@person)
  220. found_person = @person_class.first(:_id => @person.id)
  221. assert_in_map(found_person)
  222. end
  223. should "return nil if not found" do
  224. @person_class.first(:name => 'Bill').should be_nil
  225. end
  226. end
  227. context "for object in map" do
  228. setup do
  229. @person = @person_class.create(:name => 'Fred')
  230. end
  231. should "not query database" do
  232. expect_no_queries
  233. @person_class.first(:_id => @person.id)
  234. end
  235. should "return exact object" do
  236. assert_in_map(@person)
  237. found_person = @person_class.first(:_id => @person.id)
  238. found_person.should equal(@person)
  239. end
  240. end
  241. end
  242. context "#all" do
  243. should "add all documents to map" do
  244. person1 = @person_class.create(:name => 'Fred')
  245. person2 = @person_class.create(:name => 'Bill')
  246. person3 = @person_class.create(:name => 'Jesse')
  247. @person_class.identity_map.clear
  248. people = @person_class.all(:_id => [person1.id, person2.id, person3.id])
  249. assert_in_map(people)
  250. end
  251. should "add missing documents to map and return existing ones" do
  252. person1 = @person_class.create(:name => 'Fred')
  253. @person_class.identity_map.clear
  254. person2 = @person_class.create(:name => 'Bill')
  255. person3 = @person_class.create(:name => 'Jesse')
  256. assert_not_in_map(person1)
  257. assert_in_map(person2, person3)
  258. people = @person_class.all(:_id => [person1.id, person2.id, person3.id])
  259. # people.first is making sure one that wasn't mapped now is
  260. assert_in_map(people.first, person2, person3)
  261. end
  262. end
  263. context "#find_by_id" do
  264. setup do
  265. @person = @person_class.create(:name => 'Bill')
  266. end
  267. should "return nil for document id not found in collection" do
  268. assert_in_map(@person)
  269. @person_class.find_by_id(BSON::ObjectId.new).should be_nil
  270. end
  271. end
  272. context "querying and selecting certain fields" do
  273. setup do
  274. @person = @person_class.create(:name => 'Bill')
  275. @person_class.identity_map.clear
  276. end
  277. should "not add to map" do
  278. assert_not_in_map(@person)
  279. @person_class.first(:_id => @person.id, :select => 'name').should == @person
  280. @person_class.first(:_id => @person.id, 'fields' => ['name']).should == @person
  281. @person_class.last(:_id => @person.id, :select => 'name', :order => 'name').should == @person
  282. @person_class.fields(:name).find(@person.id).should == @person
  283. @person_class.all(:_id => @person.id, :select => 'name').should == [@person]
  284. assert_not_in_map(@person)
  285. end
  286. should "return nil if not found" do
  287. @person_class.fields(:name).find(BSON::ObjectId.new).should be_nil
  288. end
  289. end
  290. context "single collection inherited models" do
  291. setup do
  292. class ::Item
  293. include MongoMapper::Document
  294. plugin MongoMapper::Plugins::IdentityMap
  295. key :title, String
  296. key :parent_id, ObjectId
  297. belongs_to :parent, :class_name => 'Item'
  298. one :blog, :class_name => 'Blog', :foreign_key => 'parent_id'
  299. end
  300. Item.collection.remove
  301. class ::Blog < ::Item; end
  302. class ::BlogPost < ::Item
  303. key :blog_id, ObjectId
  304. belongs_to :blog
  305. end
  306. end
  307. teardown do
  308. Object.send :remove_const, 'Item' if defined?(::Item)
  309. Object.send :remove_const, 'Blog' if defined?(::Blog)
  310. Object.send :remove_const, 'BlogPost' if defined?(::BlogPost)
  311. end
  312. should "share the same identity map" do
  313. blog = Blog.create(:title => 'Jill')
  314. assert_in_map(blog)
  315. Item.identity_map.should equal(Blog.identity_map)
  316. end
  317. should "not query when finding by _id and _type" do
  318. blog = Blog.create(:title => 'Blog')
  319. post = BlogPost.create(:title => 'Mongo Rocks', :blog => blog)
  320. Item.identity_map.clear
  321. blog = Item.find(blog.id)
  322. post = Item.find(post.id)
  323. assert_in_map(blog, post)
  324. expect_no_queries
  325. post.blog
  326. Blog.find(blog.id)
  327. end
  328. should "load from map when using parent collection inherited class" do
  329. blog = Blog.create(:title => 'Jill')
  330. Item.find(blog.id).should equal(blog)
  331. end
  332. should "work correctly with belongs to proxy" do
  333. root = Item.create(:title => 'Root')
  334. assert_in_map(root)
  335. blog = Blog.create(:title => 'Jill', :parent => root)
  336. assert_in_map(blog)
  337. root.should equal(blog.parent.target)
  338. end
  339. should "work correctly with one proxy" do
  340. blog = Blog.create(:title => 'Jill')
  341. assert_in_map(blog)
  342. root = Item.create(:title => 'Root', :blog => blog)
  343. assert_in_map(root)
  344. blog.should equal(root.blog.target)
  345. end
  346. should "work correctly with one proxy create" do
  347. root = Item.create(:title => 'Root')
  348. blog = root.create_blog(:title => 'Blog')
  349. blog.parent.should equal(root)
  350. end
  351. end
  352. context "without identity map" do
  353. should "not add to map on save" do
  354. @post_class.without_identity_map do
  355. post = @post_class.create(:title => 'Bill')
  356. assert_not_in_map(post)
  357. end
  358. end
  359. should "not remove from map on delete" do
  360. post = @post_class.create(:title => 'Bill')
  361. assert_in_map(post)
  362. @post_class.without_identity_map do
  363. post.destroy
  364. end
  365. assert_in_map(post)
  366. end
  367. should "not add to map when loading" do
  368. @post_class.without_identity_map do
  369. post = @post_class.load({'_id' => BSON::ObjectId.new, 'title' => 'Awesome!'})
  370. assert_not_in_map(post)
  371. end
  372. end
  373. should "not load from map when loading" do
  374. post = @post_class.create(:title => 'Awesome!')
  375. @post_class.without_identity_map do
  376. loaded = @post_class.load('_id' => post._id, 'title' => 'Awesome!')
  377. loaded.should_not equal(post)
  378. end
  379. end
  380. should "not load attributes from map when finding" do
  381. post = @post_class.create(:title => 'Awesome!')
  382. post.title = 'Temporary'
  383. @post_class.without_identity_map do
  384. @post_class.find(post.id).title.should == 'Awesome!'
  385. end
  386. end
  387. context "all" do
  388. should "not add to map" do
  389. @post_class.without_identity_map do
  390. post1 = @post_class.create(:title => 'Foo')
  391. post2 = @post_class.create(:title => 'Bar')
  392. @post_class.identity_map.clear
  393. assert_not_in_map(@post_class.all)
  394. end
  395. end
  396. end
  397. context "first" do
  398. should "not add to map" do
  399. @post_class.without_identity_map do
  400. post1 = @post_class.create(:title => 'Foo')
  401. post2 = @post_class.create(:title => 'Bar')
  402. @post_class.identity_map.clear
  403. assert_not_in_map(@post_class.first)
  404. end
  405. end
  406. end
  407. context "last" do
  408. should "not add to map" do
  409. @post_class.without_identity_map do
  410. post1 = @post_class.create(:title => 'Foo')
  411. post2 = @post_class.create(:title => 'Bar')
  412. @post_class.identity_map.clear
  413. assert_not_in_map(@post_class.last(:order => 'title'))
  414. end
  415. end
  416. end
  417. end
  418. end
  419. end