require "cases/helper" require 'models/developer' require 'models/project' require 'models/company' require 'models/topic' require 'models/reply' require 'models/computer' require 'models/customer' require 'models/order' require 'models/post' require 'models/author' require 'models/tag' require 'models/tagging' require 'models/comment' require 'models/sponsor' require 'models/member' require 'models/essay' require 'models/subscriber' require "models/pirate" require "models/bird" require "models/parrot" class IdentityMapTest < ActiveRecord::TestCase fixtures :accounts, :companies, :developers, :projects, :topics, :developers_projects, :computers, :authors, :author_addresses, :posts, :tags, :taggings, :comments, :subscribers ############################################################################## # Basic tests checking if IM is functioning properly on basic find operations# ############################################################################## def test_find_id assert_same(Client.find(3), Client.find(3)) end def test_find_id_without_identity_map ActiveRecord::IdentityMap.without do assert_not_same(Client.find(3), Client.find(3)) end end def test_find_id_use_identity_map ActiveRecord::IdentityMap.enabled = false ActiveRecord::IdentityMap.use do assert_same(Client.find(3), Client.find(3)) end ActiveRecord::IdentityMap.enabled = true end def test_find_pkey assert_same( Subscriber.find('swistak'), Subscriber.find('swistak') ) end def test_find_by_id assert_same( Client.find_by_id(3), Client.find_by_id(3) ) end def test_find_by_string_and_numeric_id assert_same( Client.find_by_id("3"), Client.find_by_id(3) ) end def test_find_by_pkey assert_same( Subscriber.find_by_nick('swistak'), Subscriber.find_by_nick('swistak') ) end def test_find_first_id assert_same( Client.find(:first, :conditions => {:id => 1}), Client.find(:first, :conditions => {:id => 1}) ) end def test_find_first_pkey assert_same( Subscriber.find(:first, :conditions => {:nick => 'swistak'}), Subscriber.find(:first, :conditions => {:nick => 'swistak'}) ) end ############################################################################## # Tests checking if IM is functioning properly on more advanced finds # # and associations # ############################################################################## def test_owner_object_is_associated_from_identity_map post = Post.find(1) comment = post.comments.first assert_no_queries do comment.post end assert_same post, comment.post.target end def test_associated_object_are_assigned_from_identity_map post = Post.find(1) post.comments.each do |comment| assert_same post, comment.post.target assert_equal post.object_id, comment.post.target.object_id end end def test_creation t1 = Topic.create("title" => "t1") t2 = Topic.find(t1.id) assert_same(t1, t2) end ############################################################################## # Tests checking dirty attribute behaviour with IM # ############################################################################## def test_loading_new_instance_should_not_update_dirty_attributes swistak = Subscriber.find(:first, :conditions => {:nick => 'swistak'}) swistak.name = "Swistak Sreberkowiec" assert_equal(["name"], swistak.changed) assert_equal({"name" => ["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes) s = Subscriber.find('swistak') assert swistak.name_changed? assert_equal("Swistak Sreberkowiec", swistak.name) end def test_loading_new_instance_should_change_dirty_attribute_original_value swistak = Subscriber.find(:first, :conditions => {:nick => 'swistak'}) swistak.name = "Swistak Sreberkowiec" Subscriber.update_all({:name => "Raczkowski Marcin"}, {:name => "Marcin Raczkowski"}) s = Subscriber.find('swistak') assert_equal({'name' => ["Raczkowski Marcin", "Swistak Sreberkowiec"]}, swistak.changes) assert_equal("Swistak Sreberkowiec", swistak.name) end def test_loading_new_instance_should_remove_dirt swistak = Subscriber.find(:first, :conditions => {:nick => 'swistak'}) swistak.name = "Swistak Sreberkowiec" assert_equal({"name" => ["Marcin Raczkowski", "Swistak Sreberkowiec"]}, swistak.changes) Subscriber.update_all({:name => "Swistak Sreberkowiec"}, {:name => "Marcin Raczkowski"}) s = Subscriber.find('swistak') assert_equal("Swistak Sreberkowiec", swistak.name) assert_equal({}, swistak.changes) assert !swistak.name_changed? end def test_has_many_associations pirate = Pirate.create!(:catchphrase => "Don' botharrr talkin' like one, savvy?") pirate.birds.create!(:name => 'Posideons Killer') pirate.birds.create!(:name => 'Killer bandita Dionne') posideons, killer = pirate.birds pirate.reload pirate.birds_attributes = [{ :id => posideons.id, :name => 'Grace OMalley' }] assert_equal 'Grace OMalley', pirate.birds.send(:load_target).find { |r| r.id == posideons.id }.name end def test_changing_associations post1 = Post.create("title" => "One post", "body" => "Posting...") post2 = Post.create("title" => "Another post", "body" => "Posting... Again...") comment = Comment.new("body" => "comment") comment.post = post1 assert comment.save assert_same(post1.comments.first, comment) comment.post = post2 assert comment.save assert_same(post2.comments.first, comment) assert_equal(0, post1.comments.size) end def test_im_with_polymorphic_has_many_going_through_join_model_with_custom_select_and_joins tag = posts(:welcome).tags.first tag_with_joins_and_select = posts(:welcome).tags.add_joins_and_select.first assert_same(tag, tag_with_joins_and_select) assert_nothing_raised(NoMethodError, "Joins/select was not loaded") { tag.author_id } end ############################################################################## # Tests checking Identity Map behaviour with preloaded associations, joins, # # includes etc. # ############################################################################## def test_find_with_preloaded_associations assert_queries(2) do posts = Post.preload(:comments) assert posts.first.comments.first end # With IM we'll retrieve post object from previous query, it'll have comments # already preloaded from first call assert_queries(1) do posts = Post.preload(:comments).to_a assert posts.first.comments.first end assert_queries(2) do posts = Post.preload(:author) assert posts.first.author end # With IM we'll retrieve post object from previous query, it'll have comments # already preloaded from first call assert_queries(1) do posts = Post.preload(:author).to_a assert posts.first.author end assert_queries(1) do posts = Post.preload(:author, :comments).to_a assert posts.first.author assert posts.first.comments.first end end def test_find_with_included_associations assert_queries(2) do posts = Post.includes(:comments) assert posts.first.comments.first end assert_queries(1) do posts = Post.scoped.includes(:comments) assert posts.first.comments.first end assert_queries(2) do posts = Post.includes(:author) assert posts.first.author end assert_queries(1) do posts = Post.includes(:author, :comments).to_a assert posts.first.author assert posts.first.comments.first end end def test_eager_loading_with_conditions_on_joined_table_preloads posts = Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id') assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author} assert_same posts.first.author.target, Author.first posts = Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => [:comments], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id') assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author} assert_same posts.first.author.target, Author.first posts = Post.find(:all, :include => :author, :joins => {:taggings => :tag}, :conditions => "tags.name = 'General'", :order => 'posts.id') assert_equal posts(:welcome, :thinking), posts assert_same posts.first.author.target, Author.first posts = Post.find(:all, :include => :author, :joins => {:taggings => {:tag => :taggings}}, :conditions => "taggings_tags.super_tag_id=2", :order => 'posts.id') assert_equal posts(:welcome, :thinking), posts assert_same posts.first.author.target, Author.first end def test_eager_loading_with_conditions_on_string_joined_table_preloads posts = assert_queries(2) do Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => "INNER JOIN comments on comments.post_id = posts.id", :conditions => "comments.body like 'Thank you%'", :order => 'posts.id') end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author} posts = assert_queries(1) do Post.find(:all, :select => 'distinct posts.*', :include => :author, :joins => ["INNER JOIN comments on comments.post_id = posts.id"], :conditions => "comments.body like 'Thank you%'", :order => 'posts.id') end assert_equal [posts(:welcome)], posts assert_equal authors(:david), assert_no_queries { posts[0].author} end ############################################################################## # Behaviour releated to saving failures ############################################################################## def test_reload_object_if_save_failed developer = Developer.first developer.salary = 0 assert !developer.save same_developer = Developer.first assert_not_same developer, same_developer assert_not_equal 0, same_developer.salary assert_not_equal developer.salary, same_developer.salary end def test_reload_object_if_forced_save_failed developer = Developer.first developer.salary = 0 assert_raise(ActiveRecord::RecordInvalid) { developer.save! } same_developer = Developer.first assert_not_same developer, same_developer assert_not_equal 0, same_developer.salary assert_not_equal developer.salary, same_developer.salary end def test_reload_object_if_update_attributes_fails developer = Developer.first developer.salary = 0 assert !developer.update_attributes(:salary => 0) same_developer = Developer.first assert_not_same developer, same_developer assert_not_equal 0, same_developer.salary assert_not_equal developer.salary, same_developer.salary end ############################################################################## # Behaviour of readonly, forzen, destroyed ############################################################################## def test_find_using_identity_map_respects_readonly_when_loading_associated_object_first author = Author.first readonly_comment = author.readonly_comments.first comment = Comment.first assert !comment.readonly? assert readonly_comment.readonly? assert_raise(ActiveRecord::ReadOnlyRecord) {readonly_comment.save} assert comment.save end def test_find_using_identity_map_respects_readonly comment = Comment.first assert !comment.readonly? author = Author.first readonly_comment = author.readonly_comments.first assert readonly_comment.readonly? assert_raise(ActiveRecord::ReadOnlyRecord) {readonly_comment.save} assert comment.save end def test_find_using_select_and_identity_map author_id, author = Author.select('id').first, Author.first assert_equal author_id, author assert_same author_id, author assert_not_nil author.name post, post_id = Post.first, Post.select('id').first assert_equal post_id, post assert_same post_id, post assert_not_nil post.title end # Currently AR is not allowing changing primary key (see Persistence#update) # So we ignore it. If this changes, this test needs to be uncommented. # def test_updating_of_pkey # assert client = Client.find(3), # client.update_attribute(:id, 666) # # assert Client.find(666) # assert_same(client, Client.find(666)) # # s = Subscriber.find_by_nick('swistak') # assert s.update_attribute(:nick, 'swistakTheJester') # assert_equal('swistakTheJester', s.nick) # # assert stj = Subscriber.find_by_nick('swistakTheJester') # assert_same(s, stj) # end end