Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
张重言
rails
提交
06c64eb6
R
rails
项目概览
张重言
/
rails
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
R
rails
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
06c64eb6
编写于
10月 15, 2010
作者:
J
Jon Leighton
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Support preloading nested through associations (using the default multi-query strategy)
上级
11508db1
变更
2
隐藏空白更改
内联
并排
Showing
2 changed file
with
216 addition
and
122 deletion
+216
-122
activerecord/lib/active_record/association_preload.rb
activerecord/lib/active_record/association_preload.rb
+76
-61
activerecord/test/cases/associations/nested_has_many_through_associations_test.rb
...associations/nested_has_many_through_associations_test.rb
+140
-61
未找到文件。
activerecord/lib/active_record/association_preload.rb
浏览文件 @
06c64eb6
...
...
@@ -202,93 +202,108 @@ def preload_has_and_belongs_to_many_association(records, reflection, preload_opt
set_association_collection_records
(
id_to_record_map
,
reflection
.
name
,
associated_records
,
'the_parent_record_id'
)
end
def
preload_has_one_association
(
records
,
reflection
,
preload_options
=
{})
return
if
records
.
first
.
send
(
"loaded_
#{
reflection
.
name
}
?"
)
id_to_record_map
,
ids
=
construct_id_map
(
records
,
reflection
.
options
[
:primary_key
])
options
=
reflection
.
options
records
.
each
{
|
record
|
record
.
send
(
"set_
#{
reflection
.
name
}
_target"
,
nil
)}
if
options
[
:through
]
through_records
=
preload_through_records
(
records
,
reflection
,
options
[
:through
])
unless
through_records
.
empty?
through_reflection
=
reflections
[
options
[
:through
]]
through_primary_key
=
through_reflection
.
primary_key_name
source
=
reflection
.
source_reflection
.
name
through_records
.
first
.
class
.
preload_associations
(
through_records
,
source
)
if
through_reflection
.
macro
==
:belongs_to
id_to_record_map
=
construct_id_map
(
records
,
through_primary_key
).
first
through_primary_key
=
through_reflection
.
klass
.
primary_key
end
through_records
.
each
do
|
through_record
|
add_preloaded_record_to_collection
(
id_to_record_map
[
through_record
[
through_primary_key
].
to_s
],
reflection
.
name
,
through_record
.
send
(
source
))
end
end
def
preload_has_one_or_has_many_association
(
records
,
reflection
,
preload_options
=
{})
if
reflection
.
macro
==
:has_many
return
if
records
.
first
.
send
(
reflection
.
name
).
loaded?
records
.
each
{
|
record
|
record
.
send
(
reflection
.
name
).
loaded
}
else
set_association_single_records
(
id_to_record_map
,
reflection
.
name
,
find_associated_records
(
ids
,
reflection
,
preload_options
),
reflection
.
primary_key_name
)
return
if
records
.
first
.
send
(
"loaded_
#{
reflection
.
name
}
?"
)
records
.
each
{
|
record
|
record
.
send
(
"set_
#{
reflection
.
name
}
_target"
,
nil
)}
end
end
def
preload_has_many_association
(
records
,
reflection
,
preload_options
=
{})
return
if
records
.
first
.
send
(
reflection
.
name
).
loaded?
options
=
reflection
.
options
primary_key_name
=
reflection
.
through_reflection_primary_key_name
id_to_record_map
,
ids
=
construct_id_map
(
records
,
primary_key_name
||
reflection
.
options
[
:primary_key
])
records
.
each
{
|
record
|
record
.
send
(
reflection
.
name
).
loaded
}
if
options
[
:through
]
through_records
=
preload_through_records
(
records
,
reflection
,
options
[
:through
])
unless
through_records
.
empty?
records_with_through_records
=
preload_through_records
(
records
,
reflection
,
options
[
:through
])
all_through_records
=
records_with_through_records
.
map
(
&
:last
).
flatten
unless
all_through_records
.
empty?
source
=
reflection
.
source_reflection
.
name
through_records
.
first
.
class
.
preload_associations
(
through_records
,
source
,
options
)
through_records
.
each
do
|
through_record
|
through_record_id
=
through_record
[
reflection
.
through_reflection_primary_key
].
to_s
add_preloaded_records_to_collection
(
id_to_record_map
[
through_record_id
],
reflection
.
name
,
through_record
.
send
(
source
))
all_through_records
.
first
.
class
.
preload_associations
(
all_through_records
,
source
,
options
)
records_with_through_records
.
each
do
|
record
,
through_records
|
source_records
=
through_records
.
map
(
&
source
).
flatten
.
compact
case
reflection
.
macro
when
:has_many
,
:has_and_belongs_to_many
add_preloaded_records_to_collection
([
record
],
reflection
.
name
,
source_records
)
when
:has_one
,
:belongs_to
add_preloaded_record_to_collection
([
record
],
reflection
.
name
,
source_records
.
first
)
end
end
end
else
set_association_collection_records
(
id_to_record_map
,
reflection
.
name
,
find_associated_records
(
ids
,
reflection
,
preload_options
),
reflection
.
primary_key_name
)
id_to_record_map
,
ids
=
construct_id_map
(
records
,
reflection
.
options
[
:primary_key
])
associated_records
=
find_associated_records
(
ids
,
reflection
,
preload_options
)
if
reflection
.
macro
==
:has_many
set_association_collection_records
(
id_to_record_map
,
reflection
.
name
,
associated_records
,
reflection
.
primary_key_name
)
else
set_association_single_records
(
id_to_record_map
,
reflection
.
name
,
associated_records
,
reflection
.
primary_key_name
)
end
end
end
alias_method
:preload_has_one_association
,
:preload_has_one_or_has_many_association
alias_method
:preload_has_many_association
,
:preload_has_one_or_has_many_association
def
preload_through_records
(
records
,
reflection
,
through_association
)
through_reflection
=
reflections
[
through_association
]
through_records
=
[]
# If the same through record is loaded twice, we want to return exactly the same
# object in the result, rather than two separate instances representing the same
# record. This is so that we can preload the source association for each record,
# and always be able to access the preloaded association regardless of where we
# refer to the record.
#
# Suffices to say, if AR had an identity map built in then this would be unnecessary.
identity_map
=
{}
options
=
{}
if
reflection
.
options
[
:source_type
]
interface
=
reflection
.
source_reflection
.
options
[
:foreign_type
]
preload_options
=
{
:conditions
=>
[
"
#{
connection
.
quote_column_name
interface
}
= ?"
,
reflection
.
options
[
:source_type
]]}
options
[
:conditions
]
=
[
"
#{
connection
.
quote_column_name
interface
}
= ?"
,
reflection
.
options
[
:source_type
]]
records
.
compact!
records
.
first
.
class
.
preload_associations
(
records
,
through_association
,
preload_options
)
else
if
reflection
.
options
[
:conditions
]
options
[
:include
]
=
reflection
.
options
[
:include
]
||
reflection
.
options
[
:source
]
options
[
:conditions
]
=
reflection
.
options
[
:conditions
]
end
options
[
:order
]
=
reflection
.
options
[
:order
]
end
records
.
first
.
class
.
preload_associations
(
records
,
through_association
,
options
)
# Dont cache the association - we would only be caching a subset
records
.
each
do
|
record
|
records
.
map
do
|
record
|
if
reflection
.
options
[
:source_type
]
# Dont cache the association - we would only be caching a subset
proxy
=
record
.
send
(
through_association
)
if
proxy
.
respond_to?
(
:target
)
through_records
.
concat
Array
.
wrap
(
proxy
.
target
)
through_records
=
proxy
.
target
proxy
.
reset
else
# this is a has_one :through reflection
through_records
<<
proxy
if
proxy
through_records
=
proxy
end
else
through_records
=
record
.
send
(
through_association
)
end
else
options
=
{}
options
[
:include
]
=
reflection
.
options
[
:include
]
||
reflection
.
options
[
:source
]
if
reflection
.
options
[
:conditions
]
options
[
:order
]
=
reflection
.
options
[
:order
]
options
[
:conditions
]
=
reflection
.
options
[
:conditions
]
records
.
first
.
class
.
preload_associations
(
records
,
through_association
,
options
)
records
.
each
do
|
record
|
through_records
.
concat
Array
.
wrap
(
record
.
send
(
through_association
))
through_records
=
Array
.
wrap
(
through_records
).
map
do
|
through_record
|
identity_map
[
through_record
]
||=
through_record
end
[
record
,
through_records
]
end
through_records
end
def
preload_belongs_to_association
(
records
,
reflection
,
preload_options
=
{})
...
...
activerecord/test/cases/associations/nested_has_many_through_associations_test.rb
浏览文件 @
06c64eb6
...
...
@@ -54,70 +54,98 @@ class NestedHasManyThroughAssociationsTest < ActiveRecord::TestCase
# 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
general
=
tags
(
:general
)
assert_equal
[
general
,
general
],
authors
(
:david
).
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?
assert_queries
(
5
)
do
authors
=
Author
.
includes
(
:tags
).
to_a
end
assert_no_queries
do
assert_equal
[
general
,
general
],
authors
.
first
.
tags
end
end
# has_many through
# Source: has_many
# Through: has_many through
def
test_has_many_through_has_many_through_with_has_many_source_reflection
luke
,
david
=
subscribers
(
:first
),
subscribers
(
:second
)
author
=
authors
(
:david
)
assert_equal
[
subscribers
(
:first
),
subscribers
(
:second
),
subscribers
(
:second
)
],
author
.
subscribers
assert_equal
[
luke
,
david
,
david
],
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
assert_queries
(
4
)
do
authors
=
Author
.
includes
(
:subscribers
).
to_a
end
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
# 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
founding
=
member_types
(
:founding
)
assert_equal
[
founding
],
members
(
:groucho
).
nested_member_types
members
=
Member
.
joins
(
:nested_member_types
).
where
(
'member_types.id'
=>
member_types
(
:founding
)
.
id
)
members
=
Member
.
joins
(
:nested_member_types
).
where
(
'member_types.id'
=>
founding
.
id
)
assert_equal
[
members
(
:groucho
)],
members
members
=
Member
.
includes
(
:nested_member_types
)
assert_equal
[
member_types
(
:founding
)],
members
.
first
.
nested_member_types
assert_queries
(
4
)
do
members
=
Member
.
includes
(
:nested_member_types
).
to_a
end
assert_no_queries
do
assert_equal
[
founding
],
members
.
first
.
nested_member_types
end
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
mustache
=
sponsors
(
:moustache_club_sponsor_for_groucho
)
members
=
Member
.
joins
(
:nested_sponsors
).
where
(
'sponsors.id'
=>
sponsors
(
:moustache_club_sponsor_for_groucho
).
id
)
assert_equal
[
mustache
],
members
(
:groucho
).
nested_sponsors
members
=
Member
.
joins
(
:nested_sponsors
).
where
(
'sponsors.id'
=>
mustache
.
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
assert_queries
(
4
)
do
members
=
Member
.
includes
(
:nested_sponsors
).
to_a
end
assert_no_queries
do
assert_equal
[
mustache
],
members
.
first
.
nested_sponsors
end
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
groucho_details
,
other_details
=
member_details
(
:groucho
),
member_details
(
:some_other_guy
)
assert_equal
[
groucho_details
,
other_details
],
members
(
:groucho
).
organization_member_details
members
=
Member
.
joins
(
:organization_member_details
).
where
(
'member_details.id'
=>
member_details
(
:groucho
).
id
)
...
...
@@ -127,127 +155,178 @@ def test_has_many_through_has_one_with_has_many_through_source_reflection
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
assert_queries
(
4
)
do
members
=
Member
.
includes
(
:organization_member_details
).
to_a
end
assert_no_queries
do
assert_equal
[
groucho_details
,
other_details
],
members
.
first
.
organization_member_details
end
end
# has_many through
# Source: has_many
# Through: has_one through
def
test_has_many_through_has_one_through_with_has_many_source_reflection
assert_equal
[
member_details
(
:groucho
),
member_details
(
:some_other_guy
)],
members
(
:groucho
).
organization_member_details_2
groucho_details
,
other_details
=
member_details
(
:groucho
),
member_details
(
:some_other_guy
)
assert_equal
[
groucho_details
,
other_details
],
members
(
:groucho
).
organization_member_details_2
members
=
Member
.
joins
(
:organization_member_details_2
).
where
(
'member_details.id'
=>
member_details
(
:groucho
)
.
id
)
where
(
'member_details.id'
=>
groucho_details
.
id
)
assert_equal
[
members
(
:groucho
),
members
(
:some_other_guy
)],
members
members
=
Member
.
joins
(
:organization_member_details_2
).
where
(
'member_details.id'
=>
9
)
assert
members
.
empty?
# TODO: Make this work
# members = Member.includes(:organization_member_details_2)
# assert_equal [member_details(:groucho), member_details(:some_other_guy)],
# members.first.organization_member_details_2
assert_queries
(
4
)
do
members
=
Member
.
includes
(
:organization_member_details_2
).
to_a
end
assert_no_queries
do
assert_equal
[
groucho_details
,
other_details
],
members
.
first
.
organization_member_details_2
end
end
# has_many through
# Source: has_and_belongs_to_many
# Through: has_many
def
test_has_many_through_has_many_with_has_and_belongs_to_many_source_reflection
assert_equal
[
categories
(
:general
),
categories
(
:cooking
)],
authors
(
:bob
).
post_categories
general
,
cooking
=
categories
(
:general
),
categories
(
:cooking
)
assert_equal
[
general
,
cooking
],
authors
(
:bob
).
post_categories
authors
=
Author
.
joins
(
:post_categories
).
where
(
'categories.id'
=>
c
ategories
(
:cooking
)
.
id
)
authors
=
Author
.
joins
(
:post_categories
).
where
(
'categories.id'
=>
c
ooking
.
id
)
assert_equal
[
authors
(
:bob
)],
authors
authors
=
Author
.
includes
(
:post_categories
)
assert_equal
[
categories
(
:general
),
categories
(
:cooking
)],
authors
[
2
].
post_categories
assert_queries
(
3
)
do
authors
=
Author
.
includes
(
:post_categories
).
to_a
end
assert_no_queries
do
assert_equal
[
general
,
cooking
],
authors
[
2
].
post_categories
end
end
# has_many through
# Source: has_many
# Through: has_and_belongs_to_many
def
test_has_many_through_has_and_belongs_to_many_with_has_many_source_reflection
assert_equal
[
comments
(
:greetings
),
comments
(
:more_greetings
)],
categories
(
:technology
).
post_comments
greetings
,
more
=
comments
(
:greetings
),
comments
(
:more_greetings
)
assert_equal
[
greetings
,
more
],
categories
(
:technology
).
post_comments
categories
=
Category
.
joins
(
:post_comments
).
where
(
'comments.id'
=>
comments
(
:more_greetings
)
.
id
)
categories
=
Category
.
joins
(
:post_comments
).
where
(
'comments.id'
=>
more
.
id
)
assert_equal
[
categories
(
:general
),
categories
(
:technology
)],
categories
# TODO: Make this work
# categories = Category.includes(:post_comments)
# assert_equal [comments(:greetings), comments(:more_greetings)], categories[1].post_comments
assert_queries
(
3
)
do
categories
=
Category
.
includes
(
:post_comments
).
to_a
end
assert_no_queries
do
assert_equal
[
greetings
,
more
],
categories
[
1
].
post_comments
end
end
# has_many through
# Source: has_many through a habtm
# Through: has_many through
def
test_has_many_through_has_many_with_has_many_through_habtm_source_reflection
assert_equal
[
comments
(
:greetings
),
comments
(
:more_greetings
)],
authors
(
:bob
).
category_post_comments
greetings
,
more
=
comments
(
:greetings
),
comments
(
:more_greetings
)
assert_equal
[
greetings
,
more
],
authors
(
:bob
).
category_post_comments
authors
=
Author
.
joins
(
:category_post_comments
).
where
(
'comments.id'
=>
comments
(
:does_it_hurt
).
id
)
assert_equal
[
authors
(
:david
),
authors
(
:mary
)],
authors
comments
=
Author
.
joins
(
:category_post_comments
)
assert_equal
[
comments
(
:greetings
),
comments
(
:more_greetings
)],
comments
[
2
].
category_post_comments
assert_queries
(
5
)
do
authors
=
Author
.
includes
(
:category_post_comments
).
to_a
end
assert_no_queries
do
assert_equal
[
greetings
,
more
],
authors
[
2
].
category_post_comments
end
end
# has_many through
# Source: belongs_to
# Through: has_many through
def
test_has_many_through_has_many_through_with_belongs_to_source_reflection
author
=
authors
(
:david
)
assert_equal
[
tags
(
:general
),
tags
(
:general
)],
author
.
tagging_tags
general
=
tags
(
:general
)
assert_equal
[
general
,
general
],
authors
(
:david
).
tagging_tags
authors
=
Author
.
joins
(
:tagging_tags
).
where
(
'tags.id'
=>
tags
(
:general
).
id
)
assert_equal
[
authors
(
:david
)],
authors
.
uniq
# TODO: Make this work
# authors = Author.includes(:tagging_tags)
# assert_equal [tags(:general), tags(:general)], authors.first.tagging_tags
assert_queries
(
5
)
do
authors
=
Author
.
includes
(
:tagging_tags
).
to_a
end
assert_no_queries
do
assert_equal
[
general
,
general
],
authors
.
first
.
tagging_tags
end
end
# has_many through
# Source: has_many through
# Through: belongs_to
def
test_has_many_through_belongs_to_with_has_many_through_source_reflection
assert_equal
[
taggings
(
:welcome_general
),
taggings
(
:thinking_general
)],
categorizations
(
:david_welcome_general
).
post_taggings
welcome_general
,
thinking_general
=
taggings
(
:welcome_general
),
taggings
(
:thinking_general
)
assert_equal
[
welcome_general
,
thinking_general
],
categorizations
(
:david_welcome_general
).
post_taggings
categorizations
=
Categorization
.
joins
(
:post_taggings
).
where
(
'taggings.id'
=>
taggings
(
:welcome_general
)
.
id
)
categorizations
=
Categorization
.
joins
(
:post_taggings
).
where
(
'taggings.id'
=>
welcome_general
.
id
)
assert_equal
[
categorizations
(
:david_welcome_general
)],
categorizations
categorizations
=
Categorization
.
includes
(
:post_taggings
)
assert_equal
[
taggings
(
:welcome_general
),
taggings
(
:thinking_general
)],
categorizations
.
first
.
post_taggings
assert_queries
(
4
)
do
categorizations
=
Categorization
.
includes
(
:post_taggings
).
to_a
end
assert_no_queries
do
assert_equal
[
welcome_general
,
thinking_general
],
categorizations
.
first
.
post_taggings
end
end
# 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
founding
=
member_types
(
:founding
)
assert_equal
founding
,
members
(
:groucho
).
nested_member_type
members
=
Member
.
joins
(
:nested_member_type
).
where
(
'member_types.id'
=>
member_types
(
:founding
)
.
id
)
members
=
Member
.
joins
(
:nested_member_type
).
where
(
'member_types.id'
=>
founding
.
id
)
assert_equal
[
members
(
:groucho
)],
members
members
=
Member
.
includes
(
:nested_member_type
)
assert_equal
member_types
(
:founding
),
members
.
first
.
nested_member_type
assert_queries
(
4
)
do
members
=
Member
.
includes
(
:nested_member_type
).
to_a
end
assert_no_queries
do
assert_equal
founding
,
members
.
first
.
nested_member_type
end
end
# has_one through
# Source: belongs_to
# Through: has_one through
def
test_has_one_through_has_one_through_with_belongs_to_source_reflection
assert_equal
categories
(
:general
),
members
(
:groucho
).
club_category
general
=
categories
(
:general
)
assert_equal
general
,
members
(
:groucho
).
club_category
members
=
Member
.
joins
(
:club_category
).
where
(
'categories.id'
=>
categories
(
:technology
).
id
)
assert_equal
[
members
(
:blarpy_winkup
)],
members
# TODO: Make this work
# members = Member.includes(:club_category)
# assert_equal categories(:general), members.first.club_category
assert_queries
(
4
)
do
members
=
Member
.
includes
(
:club_category
).
to_a
end
assert_no_queries
do
assert_equal
general
,
members
.
first
.
club_category
end
end
def
test_distinct_has_many_through_a_has_many_through_association_on_source_reflection
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录