require File.dirname(__FILE__) + '/../../test_helper' require 'active_record/associations/has_many_association' class HasManyAssociationTest < Test::Unit::TestCase include ActiveRecord::Associations def setup cache.clear rescue nil end uses_mocha 'HasManyAssociationTest' do def test_should_always_use_cache_for_all_instances_which_reference_the_same_record cache.expects(:read).with("#{cache_key}/cached_posts").returns association_proxy expected = authors(:luca).cached_posts actual = Author.first.cached_posts assert_equal expected, actual end def test_should_expire_cache_on_update author = authors(:luca) old_cache_key = author.cache_key cache.expects(:read).with("#{cache_key}/cached_posts").returns association_proxy cache.expects(:delete).with("#{cache_key}/cached_posts").returns true cache.expects(:delete).with("#{cache_key}/cached_comments").returns true cache.expects(:delete).with("#{cache_key}/cached_posts_with_comments").returns true author.cached_posts # force cache loading author.update_attributes :first_name => author.first_name.upcase # assert_not_equal old_cache_key, author.cache_key assert_equal posts_by_author(:luca), authors(:luca).cached_posts end def test_should_use_cache_when_find_with_scope cache.expects(:read).with("#{cache_key}/cached_posts").returns association_proxy post = authors(:luca).cached_posts.find(posts(:welcome).id) assert_equal posts(:welcome), post end def test_should_use_cache_when_find_with_scope_using_multiple_ids cache.expects(:read).with("#{cache_key}/cached_posts").returns association_proxy ids = posts_by_author(:luca).map(&:id) assert_equal posts_by_author(:luca), authors(:luca).cached_posts.find(ids) end def test_should_use_cache_when_fetch_first_from_collection cache.expects(:read).with("#{cache_key}/cached_posts").returns association_proxy assert_equal [ posts_by_author(:luca).first ], authors(:luca).cached_posts.first(1) end def test_should_use_cache_when_fetch_last_from_collection cache.expects(:read).with("#{cache_key}/cached_posts").returns association_proxy assert_equal [ posts_by_author(:luca).last ], authors(:luca).cached_posts.last(1) end def test_should_unload_cache_when_reset_collection cache.expects(:read).with("#{cache_key}/cached_posts").times(2).returns association_proxy assert_false authors(:luca).cached_posts.reset assert_equal posts_by_author(:luca), authors(:luca).cached_posts end def test_should_not_use_cache_on_collection_sum # calculations aren't supported for now cache.expects(:read).with("#{blogs(:weblog).cache_key}/authors").never assert_equal authors_by_blog(:weblog).map(&:age).sum, blogs(:weblog).authors.sum(:age) end def test_should_not_use_cache_on_false_cached_option cache.expects(:read).never authors(:luca).posts authors(:luca).posts(true) # force reload end def test_should_cache_associated_objects cache.expects(:read).with("#{cache_key}/cached_posts").times(2).returns(posts_by_author(:luca)) posts = authors(:luca).cached_posts assert_equal posts, authors(:luca).cached_posts end def test_should_safely_use_pagination # pagination for now bypass cache and using database. # the expectation is due to #cached_posts invocation. cache.expects(:read).with("#{cache_key}/cached_posts").returns association_proxy posts = authors(:luca).cached_posts.paginate(:all, :page => 1, :per_page => 1) assert_equal [ posts_by_author(:luca).first ], posts end def test_should_reload_association_and_refresh_the_cache_on_force_reload cache.expects(:read).with("#{cache_key}/cached_posts").times(2).returns(posts_by_author(:luca)) cache.expects(:write).times(3).returns true reloaded_posts = authors(:luca).cached_posts(true) assert_equal reloaded_posts, authors(:luca).cached_posts end def test_should_cache_associated_ids ids = posts_by_author(:luca).map(&:id) cache.expects(:read).with("#{cache_key}/cached_posts").returns(posts_by_author(:luca)) cache.expects(:fetch).with("#{cache_key}/cached_post_ids").returns ids assert_equal ids, authors(:luca).cached_post_ids end def test_should_not_cache_associated_ids_on_false_cached_option cache.expects(:fetch).never authors(:luca).post_ids end def test_should_cache_all_eager_loaded_objects cache.expects(:read).with("#{cache_key}/cached_posts_with_comments").returns(posts_by_author(:luca, true)) posts = authors(:luca).cached_posts_with_comments assert_equal posts, authors(:luca).cached_posts_with_comments end def test_should_not_cache_eager_loaded_objects_on_false_cached_option cache.expects(:read).never authors(:luca).posts_with_comments end def test_should_cache_polymorphic_associations cache.expects(:read).with("#{posts(:cached_models).cache_key}/cached_tags").returns(tags_by_post(:cached_models)) tags = posts(:cached_models).cached_tags assert_equal tags, posts(:cached_models).cached_tags end def test_should_not_cache_polymorphic_associations_on_false_cached_option cache.expects(:read).never posts(:cached_models).tags end def test_should_cache_habtm_associations cache.expects(:read).with("#{cache_key}/cached_comments").returns(comments_by_author(:luca)) comments = authors(:luca).cached_comments assert_equal comments, authors(:luca).cached_comments end def test_should_not_cache_habtm_associations_on_false_cached_option cache.expects(:read).never authors(:luca).comments end def test_should_refresh_cache_when_associated_elements_change cache.expects(:read).with("#{cache_key}/cached_posts").never cache.expects(:delete).with("#{cache_key}/cached_posts").returns true post = authors(:luca).cached_posts.last # force cache loading and fetch a post post.update_attributes :title => 'Cached Models!' assert_equal posts_by_author(:luca), authors(:luca).cached_posts end def test_should_refresh_cache_when_pushing_element_to_association cache.expects(:read).with("#{cache_key}/cached_posts").times(2).returns association_proxy cache.expects(:write).with("#{cache_key}/cached_posts", association_proxy).returns true post = create_post :author_id => nil authors(:luca).cached_posts << post assert_equal posts_by_author(:luca), authors(:luca).cached_posts end def test_should_not_use_cache_when_pushing_element_to_association_on_false_cached_option cache.expects(:write).never post = create_post :author_id => nil authors(:luca).posts << post end def test_should_not_use_cache_when_pushing_element_to_association_belonging_to_anotner_model_on_false_cached_option cache.expects(:delete).with("#{blogs(:weblog).cache_key}/posts").never cache.expects(:delete).with("#{cache_key}/cached_posts_with_comments").never cache.expects(:delete).with("#{cache_key}/cached_posts").returns true cache.expects(:delete).with("#{posts(:cached_models).cache_key}/cached_tags").returns true post = blogs(:weblog).posts.last blogs(:blog).posts << post assert_equal posts_by_blog(:blog), blogs(:blog).posts end def test_should_not_use_cache_when_pushing_element_to_polymorphic_association_belonging_to_another_model_on_false_cached_option cache.expects(:delete).never tag = posts(:welcome).tags.last posts(:cached_models).tags << tag assert_equal tags_by_post(:cached_models), posts(:cached_models).tags end def test_should_update_cache_when_directly_assigning_a_new_collection posts = [ posts_by_author(:luca).first ] cache.expects(:read).with("#{cache_key}/cached_posts").times(2).returns association_proxy authors(:luca).cached_posts = posts assert_equal posts_by_author(:luca), authors(:luca).cached_posts end def test_should_use_cache_for_collection_size cache.expects(:read).with("#{cache_key}/cached_posts").returns association_proxy assert_equal posts_by_author(:luca).size, authors(:luca).cached_posts.size end def test_should_use_cache_and_return_uniq_records_for_collection_size_on_uniq_option cache.expects(:read).with("#{cache_key}/uniq_cached_posts").never # wuh?! assert_equal posts_by_author(:luca).size, authors(:luca).uniq_cached_posts.size end def test_should_use_cache_for_collection_length cache.expects(:read).with("#{cache_key}/cached_posts").returns association_proxy assert_equal posts_by_author(:luca).length, authors(:luca).cached_posts.length end def test_should_use_cache_for_collection_empty cache.expects(:read).with("#{cache_key}/cached_posts").returns association_proxy assert_equal posts_by_author(:luca).empty?, authors(:luca).cached_posts.empty? end def test_should_use_cache_for_collection_any cache.expects(:read).with("#{cache_key}/cached_posts").returns association_proxy assert_equal posts_by_author(:luca).any?, authors(:luca).cached_posts.any? end def test_should_use_cache_for_collection_include cache.expects(:read).with("#{cache_key}/cached_posts").returns association_proxy post = posts_by_author(:luca).first assert authors(:luca).cached_posts.include?(post) end end uses_memcached 'HasManyAssociationTest' do def test_should_refresh_caches_when_pushing_element_to_association_belonging_to_another_model post = authors(:chuck).cached_posts.last authors(:luca).cached_posts << post assert_equal posts_by_author(:luca), authors(:luca).cached_posts assert_equal posts_by_author(:chuck), authors(:chuck).cached_posts end def test_should_refresh_caches_when_pushing_element_to_polymorphic_association_belonging_to_another_model tag = posts(:welcome).cached_tags.last posts(:cached_models).cached_tags << tag # NOTE for some weird reason the assertion fails, even if the collections are equals. # I forced the comparision between the ids. assert_equal tags_by_post(:cached_models).map(&:id).sort, posts(:cached_models).cached_tags.map(&:id).sort end def test_should_update_cache_when_pushing_element_with_build author = authors(:luca) post = author.cached_posts.build post_options post.save assert_equal posts_by_author(:luca), author.cached_posts end def test_should_update_cache_when_pushing_element_with_create author = authors(:luca) author.cached_posts.create post_options(:title => "CM Overview") assert_equal posts_by_author(:luca), author.cached_posts end def test_should_update_cache_when_pushing_element_with_create_bang_method author = authors(:luca) author.cached_posts.create! post_options(:title => "CM Overview!!") assert_equal posts_by_author(:luca), author.cached_posts end def test_should_expire_cache_when_delete_all_elements_from_collection authors(:luca).cached_posts.delete_all assert_equal posts_by_author(:luca), authors(:luca).cached_posts end def test_should_expire_cache_when_destroy_all_elements_from_collection authors(:luca).cached_posts.destroy_all assert_equal posts_by_author(:luca), authors(:luca).cached_posts end def test_should_update_cache_when_clearing_collection authors(:luca).cached_posts.clear assert_equal posts_by_author(:luca), authors(:luca).cached_posts end def test_should_update_cache_when_clearing_collection_with_dependent_destroy_option authors(:luca).cached_dependent_posts.clear assert_equal posts_by_author(:luca), authors(:luca).cached_dependent_posts end def test_should_update_cache_when_deleting_element_from_collection authors(:luca).cached_posts.delete(posts_by_author(:luca).first) assert_equal posts_by_author(:luca), authors(:luca).cached_posts end def test_should_update_cache_when_replace_collection post = create_post; post.save posts = [ posts_by_author(:luca).first, post ] authors(:luca).cached_posts.replace(posts) assert_equal posts_by_author(:luca), authors(:luca).cached_posts end def test_should_not_expire_cache_on_update_on_missing_updated_at author = authors(:luca) old_cache_key = author.cache_key author.cached_posts # force cache loading author.update_attributes :first_name => author.first_name.upcase # assert_not_equal old_cache_key, author.cache_key assert_equal posts_by_author(:luca), authors(:luca).cached_posts end end private def posts_by_author(author, include_comments = false) conditions = include_comments ? { :include => :comments } : { } Post.find_all_by_author_id(authors(author).id, conditions) end def posts_by_blog(blog) Post.find_all_by_blog_id(blogs(blog).id) end def tags_by_post(post) Tag.find_all_by_taggable_id(posts(post).id) end def comments_by_author(author) Comment.find(:all, :conditions => ["post_id IN (?)", authors(author).post_ids]) end def authors_by_blog(blog) Author.find_all_by_blog_id(blogs(blog).id) end def association_proxy(author = :luca) HasManyAssociation.new(authors(author), Author.reflect_on_association(:cached_posts)) end def authors_association_proxy(blog = :weblog) HasManyAssociation.new(blogs(blog), Blog.reflect_on_association(:authors)) end def tags_association_proxy(post = :welcome) HasManyAssociation.new(posts(post), Post.reflect_on_association(:cached_tags)) end def comments_association_proxy(author = :luca) HasManyThroughAssociation.new(authors(author), Author.reflect_on_association(:cached_comments)) end def create_post(options = {}) Post.new({ :author_id => 1, :title => 'CachedModels', :text => 'Introduction to CachedModels plugin', :published_at => 1.week.ago }.merge(options)) end def post_options(options = {}) { :blog_id => blogs(:weblog).id, :title => "Cached models review", :text => "Cached models review..", :published_at => 1.week.ago }.merge(options) end def cache_key @cache_key ||= authors(:luca).cache_key end end