aboutsummaryrefslogtreecommitdiffstats
path: root/activerecord/test/cases/associations/nested_has_many_through_associations_test.rb
blob: 8a4ab627bb93636af6bd3d114c41a502a0781469 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
require "cases/helper"
require 'models/author'
require 'models/post'
require 'models/person'
require 'models/reference'
require 'models/job'
require 'models/reader'
require 'models/comment'
require 'models/tag'
require 'models/tagging'
require 'models/owner'
require 'models/pet'
require 'models/toy'
require 'models/contract'
require 'models/company'
require 'models/developer'
require 'models/subscriber'
require 'models/book'
require 'models/subscription'
require 'models/rating'

# NOTE: Some of these tests might not really test "nested" HMT associations, as opposed to ones which
# are just one level deep. But it's all the same thing really, as the "nested" code is being 
# written in a generic way which applies to "non-nested" HMT associations too. So let's just shove
# all useful tests in here for now and then work out where they ought to live properly later.

class NestedHasManyThroughAssociationsTest < ActiveRecord::TestCase
  fixtures :authors, :books, :posts, :subscriptions, :subscribers, :tags, :taggings,
           :people, :readers, :references, :jobs, :ratings, :comments

  # Through associations can either use the has_many or has_one macros.
  # 
  # has_many
  #   - Source reflection can be has_many, has_one, belongs_to or has_and_belongs_to_many
  #   - Through reflection can be has_many, has_one, belongs_to or has_and_belongs_to_many
  # 
  # has_one
  #   - Source reflection can be has_one or belongs_to
  #   - Through reflection can be has_one or belongs_to
  # 
  # Additionally, the source reflection and/or through reflection may be subject to
  # polymorphism and/or STI.
  # 
  # When testing these, we need to make sure it works via loading the association directly, or
  # joining the association, or including the association. We also need to ensure that associations
  # are readonly where relevant.

  # has_many through
  # Source: has_many through
  # Through: has_many
  def test_has_many_through_a_has_many_through_association_on_source_reflection
    author = authors(:david)
    assert_equal [tags(:general), tags(:general)], author.tags
    
    # Only David has a Post tagged with General
    authors = Author.joins(:tags).where('tags.id' => tags(:general).id)
    assert_equal [authors(:david)], authors.uniq
    
    # This ensures that the polymorphism of taggings is being observed correctly
    authors = Author.joins(:tags).where('taggings.taggable_type' => 'FakeModel')
    assert authors.empty?
  end

  # has_many through
  # Source: has_many
  # Through: has_many through
  def test_has_many_through_a_has_many_through_association_on_through_reflection
    author = authors(:david)
    assert_equal [subscribers(:first), subscribers(:second), subscribers(:second)], author.subscribers
    
    # All authors with subscribers where one of the subscribers' nick is 'alterself'
    authors = Author.joins(:subscribers).where('subscribers.nick' => 'alterself')
    assert_equal [authors(:david)], authors
  end
  
  # TODO: has_many through
  # Source: has_one through
  # Through: has_one
  
  # TODO: has_many through
  # Source: has_one
  # Through: has_one through
  
  # TODO: has_many through
  # Source: has_many through
  # Through: has_one
  
  # TODO: has_many through
  # Source: has_many
  # Through: has_one through
  
  # TODO: has_many through
  # Source: has_and_belongs_to_many
  # Through: has_many
  
  # TODO: has_many through
  # Source: has_many
  # Through: has_and_belongs_to_many
  
  # TODO: has_many through
  # Source: belongs_to
  # Through: has_many through
  
  # TODO: has_many through
  # Source: has_many through
  # Through: belongs_to
  
  # TODO: has_one through
  # Source: has_one through
  # Through: has_one
  
  # TODO: has_one through
  # Source: belongs_to
  # Through: has_one through

  def test_distinct_has_many_through_a_has_many_through_association_on_source_reflection
    author = authors(:david)
    assert_equal [tags(:general)], author.distinct_tags
  end

  def test_distinct_has_many_through_a_has_many_through_association_on_through_reflection
    author = authors(:david)
    assert_equal [subscribers(:first), subscribers(:second)], author.distinct_subscribers
  end
  
  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)
    
    # 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)
    assert_equal [authors(:mary), authors(:bob)], authors.uniq.sort_by(&:id)
    
    # Check the polymorphism of taggings is being observed correctly (in both joins)
    authors = Author.joins(:similar_posts).where('taggings.taggable_type' => 'FakeModel')
    assert authors.empty?
    authors = Author.joins(:similar_posts).where('taggings_authors_join.taggable_type' => 'FakeModel')
    assert authors.empty?
  end
  
  def test_has_many_through_with_foreign_key_option_on_through_reflection
    assert_equal [posts(:welcome), posts(:authorless)], people(:david).agents_posts
    assert_equal [authors(:david)], references(:david_unicyclist).agents_posts_authors
    
    references = Reference.joins(:agents_posts_authors).where('authors.id' => authors(:david).id)
    assert_equal [references(:david_unicyclist)], references
  end
  
  def test_has_many_through_with_foreign_key_option_on_source_reflection
    assert_equal [people(:michael), people(:susan)], jobs(:unicyclist).agents
    
    jobs = Job.joins(:agents)
    assert_equal [jobs(:unicyclist), jobs(:unicyclist)], jobs
  end

  def test_has_many_through_with_sti_on_through_reflection
    ratings = posts(:sti_comments).special_comments_ratings.sort_by(&:id)
    assert_equal [ratings(:special_comment_rating), ratings(:sub_special_comment_rating)], ratings
    
    # Ensure STI is respected in the join
    scope = Post.joins(:special_comments_ratings).where(:id => posts(:sti_comments).id)
    assert scope.where("comments.type" => "Comment").empty?
    assert !scope.where("comments.type" => "SpecialComment").empty?
    assert !scope.where("comments.type" => "SubSpecialComment").empty?
  end
end