From 9ec07348749675110843c44f680da79223218db2 Mon Sep 17 00:00:00 2001 From: Jon Leighton Date: Tue, 19 Oct 2010 00:27:40 +0100 Subject: Properly support conditions on any of the reflections involved in a nested through association --- activerecord/lib/active_record/associations.rb | 15 ++- .../associations/through_association_scope.rb | 127 ++++++++++----------- activerecord/lib/active_record/reflection.rb | 53 ++++++++- .../associations/cascaded_eager_loading_test.rb | 6 +- activerecord/test/cases/associations/eager_test.rb | 4 +- .../nested_has_many_through_associations_test.rb | 40 ++++++- activerecord/test/cases/batches_test.rb | 2 +- activerecord/test/cases/finder_test.rb | 6 +- activerecord/test/cases/relations_test.rb | 18 +-- activerecord/test/fixtures/posts.yml | 14 +++ activerecord/test/fixtures/taggings.yml | 28 +++++ activerecord/test/fixtures/tags.yml | 4 + activerecord/test/models/author.rb | 7 +- activerecord/test/models/post.rb | 5 + activerecord/test/models/tagging.rb | 3 +- activerecord/test/schema/schema.rb | 1 + 16 files changed, 235 insertions(+), 98 deletions(-) (limited to 'activerecord') diff --git a/activerecord/lib/active_record/associations.rb b/activerecord/lib/active_record/associations.rb index 1111033435..75e5eb8ee4 100644 --- a/activerecord/lib/active_record/associations.rb +++ b/activerecord/lib/active_record/associations.rb @@ -2164,8 +2164,10 @@ module ActiveRecord chain = through_reflection_chain.reverse foreign_table = parent_table + index = 0 - chain.zip(@tables).each do |reflection, table| + chain.each do |reflection| + table = @tables[index] conditions = [] if reflection.source_reflection.nil? @@ -2234,13 +2236,14 @@ module ActiveRecord conditions << table[key].eq(foreign_table[foreign_key]) - conditions << reflection_conditions(reflection, table) + conditions << reflection_conditions(index, table) conditions << sti_conditions(reflection, table) - relation = relation.join(table, join_type).on(*conditions.compact) + relation = relation.join(table, join_type).on(*conditions.flatten.compact) # The current table in this iteration becomes the foreign table in the next foreign_table = table + index += 1 end relation @@ -2325,10 +2328,10 @@ module ActiveRecord @tables end - def reflection_conditions(reflection, table) - if reflection.options[:conditions] + def reflection_conditions(index, table) + @reflection.through_conditions.reverse[index].map do |condition| Arel.sql(interpolate_sql(sanitize_sql( - reflection.options[:conditions], + condition, table.table_alias || table.name ))) end diff --git a/activerecord/lib/active_record/associations/through_association_scope.rb b/activerecord/lib/active_record/associations/through_association_scope.rb index 261b4037a3..feb0a93360 100644 --- a/activerecord/lib/active_record/associations/through_association_scope.rb +++ b/activerecord/lib/active_record/associations/through_association_scope.rb @@ -24,7 +24,6 @@ module ActiveRecord end # Build SQL conditions from attributes, qualified by table name. - # TODO: Conditions on joins def construct_conditions reflection = @reflection.through_reflection_chain.last @@ -34,11 +33,12 @@ module ActiveRecord table_alias = table_aliases[reflection] end - conditions = construct_quoted_owner_attributes(reflection).map do |attr, value| + parts = construct_quoted_owner_attributes(reflection).map do |attr, value| "#{table_alias}.#{attr} = #{value}" end - conditions << sql_conditions if sql_conditions - "(" + conditions.join(') AND (') + ")" + parts += reflection_conditions(0) + + "(" + parts.join(') AND (') + ")" end # Associate attributes pointing to owner, quoted. @@ -55,23 +55,21 @@ module ActiveRecord end end - def construct_from - @reflection.table_name - end - def construct_select(custom_select = nil) distinct = "DISTINCT " if @reflection.options[:uniq] selected = custom_select || @reflection.options[:select] || "#{distinct}#{@reflection.quoted_table_name}.*" end def construct_joins(custom_joins = nil) - # p @reflection.through_reflection_chain + # TODO: Remove this at the end + #p @reflection.through_reflection_chain + #p @reflection.through_conditions "#{construct_through_joins} #{@reflection.options[:joins]} #{custom_joins}" end def construct_through_joins - joins = [] + joins, right_index = [], 1 # Iterate over each pair in the through reflection chain, joining them together @reflection.through_reflection_chain.each_cons(2) do |left, right| @@ -86,20 +84,23 @@ module ActiveRecord joins << inner_join_sql( right_table_and_alias, table_aliases[left], left.klass.primary_key, - table_aliases[right], left.primary_key_name + table_aliases[right], left.primary_key_name, + reflection_conditions(right_index) ) when :has_many, :has_one joins << inner_join_sql( right_table_and_alias, table_aliases[left], left.primary_key_name, table_aliases[right], right.klass.primary_key, - polymorphic_conditions(left, left) + polymorphic_conditions(left, left), + reflection_conditions(right_index) ) when :has_and_belongs_to_many joins << inner_join_sql( right_table_and_alias, table_aliases[left].first, left.primary_key_name, - table_aliases[right], right.klass.primary_key + table_aliases[right], right.klass.primary_key, + reflection_conditions(right_index) ) end else @@ -109,7 +110,8 @@ module ActiveRecord right_table_and_alias, table_aliases[left], left.klass.primary_key, table_aliases[right], left.source_reflection.primary_key_name, - source_type_conditions(left) + source_type_conditions(left), + reflection_conditions(right_index) ) when :has_many, :has_one if right.macro == :has_and_belongs_to_many @@ -123,7 +125,8 @@ module ActiveRecord right_table_and_alias, table_aliases[left], left.source_reflection.primary_key_name, right_table, right.klass.primary_key, - polymorphic_conditions(left, left.source_reflection) + polymorphic_conditions(left, left.source_reflection), + reflection_conditions(right_index) ) if right.macro == :has_and_belongs_to_many @@ -151,10 +154,13 @@ module ActiveRecord joins << inner_join_sql( right_table_and_alias, join_table, left.source_reflection.primary_key_name, - table_aliases[right], right.klass.primary_key + table_aliases[right], right.klass.primary_key, + reflection_conditions(right_index) ) end end + + right_index += 1 end joins.join(" ") @@ -206,18 +212,45 @@ module ActiveRecord "#{table_name} #{table_alias if table_alias != table_name}".strip end - def inner_join_sql(table, on_left_table, on_left_key, on_right_table, on_right_key, conds = nil) - "INNER JOIN %s ON %s.%s = %s.%s %s" % [ - table, - on_left_table, on_left_key, - on_right_table, on_right_key, - conds - ] + def inner_join_sql(table, on_left_table, on_left_key, on_right_table, on_right_key, *conditions) + conditions << "#{on_left_table}.#{on_left_key} = #{on_right_table}.#{on_right_key}" + conditions = conditions.flatten.compact + conditions = conditions.map { |sql| "(#{sql})" } * ' AND ' + + "INNER JOIN #{table} ON #{conditions}" + end + + def reflection_conditions(index) + reflection = @reflection.through_reflection_chain[index] + reflection_conditions = @reflection.through_conditions[index] + + conditions = [] + + if reflection.options[:as].nil? && # reflection.klass is a Module if :as is used + reflection.klass.finder_needs_type_condition? + conditions << reflection.klass.send(:type_condition).to_sql + end + + reflection_conditions.each do |condition| + sanitized_condition = reflection.klass.send(:sanitize_sql, condition) + interpolated_condition = interpolate_sql(sanitized_condition) + + if condition.is_a?(Hash) + interpolated_condition.gsub!( + @reflection.quoted_table_name, + reflection.quoted_table_name + ) + end + + conditions << interpolated_condition + end + + conditions end def polymorphic_conditions(reflection, polymorphic_reflection) if polymorphic_reflection.options[:as] - "AND %s.%s = %s" % [ + "%s.%s = %s" % [ table_aliases[reflection], "#{polymorphic_reflection.options[:as]}_type", @owner.class.quote_value(polymorphic_reflection.active_record.base_class.name) ] @@ -226,7 +259,7 @@ module ActiveRecord def source_type_conditions(reflection) if reflection.options[:source_type] - "AND %s.%s = %s" % [ + "%s.%s = %s" % [ table_aliases[reflection.through_reflection], reflection.source_reflection.options[:foreign_type].to_s, @owner.class.quote_value(reflection.options[:source_type]) @@ -245,6 +278,8 @@ module ActiveRecord end # Construct attributes for :through pointing to owner and associate. + # This method is used when adding records to the association. Since this only makes sense for + # non-nested through associations, that's the only case we have to worry about here. def construct_join_attributes(associate) # TODO: revisit this to allow it for deletion, supposing dependent option is supported raise ActiveRecord::HasManyThroughCantAssociateThroughHasOneOrManyReflection.new(@owner, @reflection) if [:has_one, :has_many].include?(@reflection.source_reflection.macro) @@ -261,48 +296,6 @@ module ActiveRecord join_attributes end - - def conditions - @conditions = build_conditions unless defined?(@conditions) - @conditions - end - - def build_conditions - association_conditions = @reflection.options[:conditions] - through_conditions = build_through_conditions - source_conditions = @reflection.source_reflection.options[:conditions] - uses_sti = !@reflection.through_reflection.klass.descends_from_active_record? - - if association_conditions || through_conditions || source_conditions || uses_sti - all = [] - - [association_conditions, source_conditions].each do |conditions| - all << interpolate_sql(sanitize_sql(conditions)) if conditions - end - - all << through_conditions if through_conditions - all << build_sti_condition if uses_sti - - all.map { |sql| "(#{sql})" } * ' AND ' - end - end - - def build_through_conditions - conditions = @reflection.through_reflection.options[:conditions] - if conditions.is_a?(Hash) - interpolate_sql(@reflection.through_reflection.klass.send(:sanitize_sql, conditions)).gsub( - @reflection.quoted_table_name, - @reflection.through_reflection.quoted_table_name) - elsif conditions - interpolate_sql(sanitize_sql(conditions)) - end - end - - def build_sti_condition - @reflection.through_reflection.klass.send(:type_condition).to_sql - end - - alias_method :sql_conditions, :conditions def ensure_not_nested if @reflection.nested? diff --git a/activerecord/lib/active_record/reflection.rb b/activerecord/lib/active_record/reflection.rb index ee63fcfce2..d8bd6c9873 100644 --- a/activerecord/lib/active_record/reflection.rb +++ b/activerecord/lib/active_record/reflection.rb @@ -253,6 +253,10 @@ module ActiveRecord def through_reflection_chain [self] end + + def through_conditions + [Array.wrap(options[:conditions])] + end def through_reflection_primary_key_name end @@ -378,9 +382,9 @@ module ActiveRecord # TODO: Documentation def through_reflection_chain @through_reflection_chain ||= begin - if source_reflection.through_reflection - # If the source reflection goes through another reflection, then the chain must start - # by getting us to the source reflection. + if source_reflection.source_reflection + # If the source reflection has its own source reflection, then the chain must start + # by getting us to that source reflection. chain = source_reflection.through_reflection_chain else # If the source reflection does not go through another reflection, then we can get @@ -396,6 +400,49 @@ module ActiveRecord end end + # Consider the following example: + # + # class Person + # has_many :articles + # has_many :comment_tags, :through => :articles + # end + # + # class Article + # has_many :comments + # has_many :comment_tags, :through => :comments, :source => :tags + # end + # + # class Comment + # has_many :tags + # end + # + # There may be conditions on Person.comment_tags, Article.comment_tags and/or Comment.tags, + # but only Comment.tags will be represented in the through_reflection_chain. So this method + # creates an array of conditions corresponding to the through_reflection_chain. Each item in + # the through_conditions array corresponds to an item in the through_reflection_chain, and is + # itself an array of conditions from an arbitrary number of relevant reflections. + def through_conditions + @through_conditions ||= begin + # Initialize the first item - which corresponds to this reflection - either by recursing + # into the souce reflection (if it is itself a through reflection), or by grabbing the + # source reflection conditions. + if source_reflection.source_reflection + conditions = source_reflection.through_conditions + else + conditions = [Array.wrap(source_reflection.options[:conditions])] + end + + # Add to it the conditions from this reflection if necessary. + conditions.first << options[:conditions] if options[:conditions] + + # Recursively fill out the rest of the array from the through reflection + conditions += through_reflection.through_conditions + + # And return + conditions + end + end + def nested? through_reflection_chain.length > 2 end diff --git a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb index 0e9c8a2639..5b24d49a7d 100644 --- a/activerecord/test/cases/associations/cascaded_eager_loading_test.rb +++ b/activerecord/test/cases/associations/cascaded_eager_loading_test.rb @@ -15,7 +15,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id") assert_equal 3, authors.size assert_equal 5, authors[0].posts.size - assert_equal 2, authors[1].posts.size + assert_equal 3, authors[1].posts.size assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} end @@ -23,7 +23,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase authors = Author.find(:all, :include=>[{:posts=>:comments}, :categorizations], :order=>"authors.id") assert_equal 3, authors.size assert_equal 5, authors[0].posts.size - assert_equal 2, authors[1].posts.size + assert_equal 3, authors[1].posts.size assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} assert_equal 1, authors[0].categorizations.size assert_equal 2, authors[1].categorizations.size @@ -56,7 +56,7 @@ class CascadedEagerLoadingTest < ActiveRecord::TestCase authors = Author.find(:all, :include=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id") assert_equal 3, authors.size assert_equal 5, authors[0].posts.size - assert_equal 2, authors[1].posts.size + assert_equal 3, authors[1].posts.size assert_equal 10, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i} end diff --git a/activerecord/test/cases/associations/eager_test.rb b/activerecord/test/cases/associations/eager_test.rb index 2ff0714e9f..6b910ae2a0 100644 --- a/activerecord/test/cases/associations/eager_test.rb +++ b/activerecord/test/cases/associations/eager_test.rb @@ -53,8 +53,8 @@ class EagerAssociationTest < ActiveRecord::TestCase def test_with_ordering list = Post.find(:all, :include => :comments, :order => "posts.id DESC") - [:misc_by_mary, :misc_by_bob, :eager_other, :sti_habtm, :sti_post_and_comments, - :sti_comments, :authorless, :thinking, :welcome + [:other_by_mary, :other_by_bob, :misc_by_mary, :misc_by_bob, :eager_other, + :sti_habtm, :sti_post_and_comments, :sti_comments, :authorless, :thinking, :welcome ].each_with_index do |post, index| assert_equal posts(post), list[index] end diff --git a/activerecord/test/cases/associations/nested_has_many_through_associations_test.rb b/activerecord/test/cases/associations/nested_has_many_through_associations_test.rb index 03ec4281d8..c39ec5d139 100644 --- a/activerecord/test/cases/associations/nested_has_many_through_associations_test.rb +++ b/activerecord/test/cases/associations/nested_has_many_through_associations_test.rb @@ -92,8 +92,6 @@ class NestedHasManyThroughAssociationsTest < ActiveRecord::TestCase assert_no_queries do assert_equal [luke, david, david], authors.first.subscribers.sort_by(&:nick) end - - # TODO: Add eager loading test using LEFT OUTER JOIN end # has_many through @@ -325,7 +323,7 @@ class NestedHasManyThroughAssociationsTest < ActiveRecord::TestCase def test_nested_has_many_through_with_a_table_referenced_multiple_times author = authors(:bob) - assert_equal [posts(:misc_by_bob), posts(:misc_by_mary)], author.similar_posts.sort_by(&:id) + assert_equal [posts(:misc_by_bob), posts(:misc_by_mary), posts(:other_by_bob), posts(:other_by_mary)], author.similar_posts.sort_by(&:id) # Mary and Bob both have posts in misc, but they are the only ones. authors = Author.joins(:similar_posts).where('posts.id' => posts(:misc_by_bob).id) @@ -406,6 +404,42 @@ class NestedHasManyThroughAssociationsTest < ActiveRecord::TestCase end end + def test_nested_has_many_through_with_conditions_on_through_associations + blue, bob = tags(:blue), authors(:bob) + + assert_equal [blue], bob.misc_post_first_blue_tags + + # Pointless condition to force single-query loading + assert_includes_and_joins_equal( + Author.where('tags.id = tags.id'), + [bob], :misc_post_first_blue_tags + ) + + assert Author.where('tags.id' => 100).joins(:misc_post_first_blue_tags).empty? + + authors = assert_queries(3) { Author.includes(:misc_post_first_blue_tags).to_a } + assert_no_queries do + assert_equal [blue], authors[2].misc_post_first_blue_tags + end + end + + def test_nested_has_many_through_with_conditions_on_source_associations + blue, bob = tags(:blue), authors(:bob) + + assert_equal [blue], bob.misc_post_first_blue_tags_2 + + # Pointless condition to force single-query loading + assert_includes_and_joins_equal( + Author.where('tags.id = tags.id'), + [bob], :misc_post_first_blue_tags_2 + ) + + authors = assert_queries(4) { Author.includes(:misc_post_first_blue_tags_2).to_a } + assert_no_queries do + assert_equal [blue], authors[2].misc_post_first_blue_tags_2 + end + end + private def assert_includes_and_joins_equal(query, expected, association) diff --git a/activerecord/test/cases/batches_test.rb b/activerecord/test/cases/batches_test.rb index 70883ad30f..9e72ac4250 100644 --- a/activerecord/test/cases/batches_test.rb +++ b/activerecord/test/cases/batches_test.rb @@ -24,7 +24,7 @@ class EachTest < ActiveRecord::TestCase end def test_each_should_execute_if_id_is_in_select - assert_queries(5) do + assert_queries(6) do Post.find_each(:select => "id, title, type", :batch_size => 2) do |post| assert_kind_of Post, post end diff --git a/activerecord/test/cases/finder_test.rb b/activerecord/test/cases/finder_test.rb index 0476fc94df..4c9475f1cd 100644 --- a/activerecord/test/cases/finder_test.rb +++ b/activerecord/test/cases/finder_test.rb @@ -123,11 +123,13 @@ class FinderTest < ActiveRecord::TestCase def test_find_all_with_limit_and_offset_and_multiple_order_clauses first_three_posts = Post.find :all, :order => 'author_id, id', :limit => 3, :offset => 0 second_three_posts = Post.find :all, :order => ' author_id,id ', :limit => 3, :offset => 3 - last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6 + third_three_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 6 + last_posts = Post.find :all, :order => ' author_id, id ', :limit => 3, :offset => 9 assert_equal [[0,3],[1,1],[1,2]], first_three_posts.map { |p| [p.author_id, p.id] } assert_equal [[1,4],[1,5],[1,6]], second_three_posts.map { |p| [p.author_id, p.id] } - assert_equal [[2,7],[2,9],[3,8]], last_posts.map { |p| [p.author_id, p.id] } + assert_equal [[2,7],[2,9],[2,11]], third_three_posts.map { |p| [p.author_id, p.id] } + assert_equal [[3,8],[3,10]], last_posts.map { |p| [p.author_id, p.id] } end diff --git a/activerecord/test/cases/relations_test.rb b/activerecord/test/cases/relations_test.rb index df4e84ca29..0d88c8eded 100644 --- a/activerecord/test/cases/relations_test.rb +++ b/activerecord/test/cases/relations_test.rb @@ -501,22 +501,22 @@ class RelationTest < ActiveRecord::TestCase def test_count posts = Post.scoped - assert_equal 9, posts.count - assert_equal 9, posts.count(:all) - assert_equal 9, posts.count(:id) + assert_equal 11, posts.count + assert_equal 11, posts.count(:all) + assert_equal 11, posts.count(:id) assert_equal 1, posts.where('comments_count > 1').count - assert_equal 7, posts.where(:comments_count => 0).count + assert_equal 9, posts.where(:comments_count => 0).count end def test_count_with_distinct posts = Post.scoped assert_equal 3, posts.count(:comments_count, :distinct => true) - assert_equal 9, posts.count(:comments_count, :distinct => false) + assert_equal 11, posts.count(:comments_count, :distinct => false) assert_equal 3, posts.select(:comments_count).count(:distinct => true) - assert_equal 9, posts.select(:comments_count).count(:distinct => false) + assert_equal 11, posts.select(:comments_count).count(:distinct => false) end def test_count_explicit_columns @@ -526,7 +526,7 @@ class RelationTest < ActiveRecord::TestCase assert_equal [0], posts.select('comments_count').where('id is not null').group('id').order('id').count.values.uniq assert_equal 0, posts.where('id is not null').select('comments_count').count - assert_equal 9, posts.select('comments_count').count('id') + assert_equal 11, posts.select('comments_count').count('id') assert_equal 0, posts.select('comments_count').count assert_equal 0, posts.count(:comments_count) assert_equal 0, posts.count('comments_count') @@ -541,12 +541,12 @@ class RelationTest < ActiveRecord::TestCase def test_size posts = Post.scoped - assert_queries(1) { assert_equal 9, posts.size } + assert_queries(1) { assert_equal 11, posts.size } assert ! posts.loaded? best_posts = posts.where(:comments_count => 0) best_posts.to_a # force load - assert_no_queries { assert_equal 7, best_posts.size } + assert_no_queries { assert_equal 9, best_posts.size } end def test_count_complex_chained_relations diff --git a/activerecord/test/fixtures/posts.yml b/activerecord/test/fixtures/posts.yml index ca6d4c2fe1..264ca164f0 100644 --- a/activerecord/test/fixtures/posts.yml +++ b/activerecord/test/fixtures/posts.yml @@ -64,3 +64,17 @@ misc_by_mary: title: misc post by mary body: hello type: Post + +other_by_bob: + id: 10 + author_id: 3 + title: other post by bob + body: hello + type: Post + +other_by_mary: + id: 11 + author_id: 2 + title: other post by mary + body: hello + type: Post diff --git a/activerecord/test/fixtures/taggings.yml b/activerecord/test/fixtures/taggings.yml index 7cc7198ded..a337cce019 100644 --- a/activerecord/test/fixtures/taggings.yml +++ b/activerecord/test/fixtures/taggings.yml @@ -38,3 +38,31 @@ misc_post_by_mary: tag_id: 2 taggable_id: 9 taggable_type: Post + +misc_by_bob_blue_first: + id: 8 + tag_id: 3 + taggable_id: 8 + taggable_type: Post + comment: first + +misc_by_bob_blue_second: + id: 9 + tag_id: 3 + taggable_id: 8 + taggable_type: Post + comment: second + +other_by_bob_blue: + id: 10 + tag_id: 3 + taggable_id: 10 + taggable_type: Post + comment: first + +other_by_mary_blue: + id: 11 + tag_id: 3 + taggable_id: 11 + taggable_type: Post + comment: first diff --git a/activerecord/test/fixtures/tags.yml b/activerecord/test/fixtures/tags.yml index 6cb886dc46..d4b7c9a4d5 100644 --- a/activerecord/test/fixtures/tags.yml +++ b/activerecord/test/fixtures/tags.yml @@ -5,3 +5,7 @@ general: misc: id: 2 name: Misc + +blue: + id: 3 + name: Blue diff --git a/activerecord/test/models/author.rb b/activerecord/test/models/author.rb index b5f702018a..c0e082836d 100644 --- a/activerecord/test/models/author.rb +++ b/activerecord/test/models/author.rb @@ -86,7 +86,7 @@ class Author < ActiveRecord::Base has_many :tagging, :through => :posts has_many :taggings, :through => :posts has_many :tags, :through => :posts - has_many :similar_posts, :through => :tags, :source => :tagged_posts + has_many :similar_posts, :through => :tags, :source => :tagged_posts, :uniq => true has_many :distinct_tags, :through => :posts, :source => :tags, :select => "DISTINCT tags.*", :order => "tags.name" has_many :post_categories, :through => :posts, :source => :categories has_many :tagging_tags, :through => :taggings, :source => :tag @@ -103,6 +103,11 @@ class Author < ActiveRecord::Base has_many :post_categories, :through => :posts, :source => :categories has_many :category_post_comments, :through => :categories, :source => :post_comments + + has_many :misc_posts, :class_name => 'Post', :conditions => "posts.title LIKE 'misc post%'" + has_many :misc_post_first_blue_tags, :through => :misc_posts, :source => :first_blue_tags + + has_many :misc_post_first_blue_tags_2, :through => :posts, :source => :first_blue_tags_2, :conditions => "posts.title LIKE 'misc post%'" scope :relation_include_posts, includes(:posts) scope :relation_include_tags, includes(:tags) diff --git a/activerecord/test/models/post.rb b/activerecord/test/models/post.rb index f3b78c3647..281586b438 100644 --- a/activerecord/test/models/post.rb +++ b/activerecord/test/models/post.rb @@ -64,6 +64,11 @@ class Post < ActiveRecord::Base has_many :funky_tags, :through => :taggings, :source => :tag has_many :super_tags, :through => :taggings has_one :tagging, :as => :taggable + + has_many :first_taggings, :as => :taggable, :class_name => 'Tagging', :conditions => "taggings.comment = 'first'" + has_many :first_blue_tags, :through => :first_taggings, :source => :tag, :conditions => "tags.name = 'Blue'" + + has_many :first_blue_tags_2, :through => :taggings, :source => :blue_tag, :conditions => "taggings.comment = 'first'" has_many :invalid_taggings, :as => :taggable, :class_name => "Tagging", :conditions => 'taggings.id < 0' has_many :invalid_tags, :through => :invalid_taggings, :source => :tag diff --git a/activerecord/test/models/tagging.rb b/activerecord/test/models/tagging.rb index a1fa1a9750..c92df88e71 100644 --- a/activerecord/test/models/tagging.rb +++ b/activerecord/test/models/tagging.rb @@ -6,5 +6,6 @@ class Tagging < ActiveRecord::Base belongs_to :tag, :include => :tagging belongs_to :super_tag, :class_name => 'Tag', :foreign_key => 'super_tag_id' belongs_to :invalid_tag, :class_name => 'Tag', :foreign_key => 'tag_id' + belongs_to :blue_tag, :class_name => 'Tag', :foreign_key => :tag_id, :conditions => "tags.name = 'Blue'" belongs_to :taggable, :polymorphic => true, :counter_cache => true -end \ No newline at end of file +end diff --git a/activerecord/test/schema/schema.rb b/activerecord/test/schema/schema.rb index 8b9c56b895..ee129162a6 100644 --- a/activerecord/test/schema/schema.rb +++ b/activerecord/test/schema/schema.rb @@ -537,6 +537,7 @@ ActiveRecord::Schema.define do t.column :super_tag_id, :integer t.column :taggable_type, :string t.column :taggable_id, :integer + t.string :comment end create_table :tags, :force => true do |t| -- cgit v1.2.3