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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
|
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'
require 'models/member'
require 'models/member_detail'
require 'models/member_type'
require 'models/sponsor'
require 'models/club'
require 'models/organization'
# 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, :members, :member_details,
:member_types, :sponsors, :clubs, :organizations
# 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_has_many_with_has_many_through_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
authors = Author.includes(:tags)
assert_equal [tags(:general), tags(:general)], authors.first.tags
# 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_has_many_through_with_has_many_source_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
# TODO: Make this work
# authors = Author.includes(:subscribers)
# assert_equal [subscribers(:first), subscribers(:second), subscribers(:second)], authors.first.subscribers
end
# has_many through
# Source: has_one through
# Through: has_one
def test_has_many_through_has_one_with_has_one_through_source_reflection
assert_equal [member_types(:founding)], members(:groucho).nested_member_types
members = Member.joins(:nested_member_types).where('member_types.id' => member_types(:founding).id)
assert_equal [members(:groucho)], members
members = Member.includes(:nested_member_types)
assert_equal [member_types(:founding)], members.first.nested_member_types
end
# has_many through
# Source: has_one
# Through: has_one through
def test_has_many_through_has_one_through_with_has_one_source_reflection
assert_equal [sponsors(:moustache_club_sponsor_for_groucho)], members(:groucho).nested_sponsors
members = Member.joins(:nested_sponsors).where('sponsors.id' => sponsors(:moustache_club_sponsor_for_groucho).id)
assert_equal [members(:groucho)], members
# TODO: Make this work
# members = Member.includes(:nested_sponsors)
# assert_equal [sponsors(:moustache_club_sponsor_for_groucho)], members.first.nested_sponsors
end
# has_many through
# Source: has_many through
# Through: has_one
def test_has_many_through_has_one_with_has_many_through_source_reflection
assert_equal [member_details(:groucho), member_details(:some_other_guy)],
members(:groucho).organization_member_details
members = Member.joins(:organization_member_details).
where('member_details.id' => member_details(:groucho).id)
assert_equal [members(:groucho), members(:some_other_guy)], members
members = Member.joins(:organization_member_details).
where('member_details.id' => 9)
assert members.empty?
members = Member.includes(:organization_member_details)
assert_equal [member_details(:groucho), member_details(:some_other_guy)],
members.first.organization_member_details
end
# 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
# has_one through
# Source: has_one through
# Through: has_one
def test_has_one_through_has_one_with_has_one_through_source_reflection
assert_equal member_types(:founding), members(:groucho).nested_member_type
members = Member.joins(:nested_member_type).where('member_types.id' => member_types(:founding).id)
assert_equal [members(:groucho)], members
members = Member.includes(:nested_member_type)
assert_equal member_types(:founding), members.first.nested_member_type
end
# 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
|