看来大家还对ActiveRecord的lazy loading和eager loading不是很清楚
ActiveRecord默认是lazy loading的,而加上:include选项后可以指定eager loading一些字段
:include - specify second-order associations that should be eager loaded when the collection is loaded.
看例子:
Post.find(:all, :include => :comments)
# => SELECT ... FROM posts LEFT OUTER JOIN comments ON ...
我再把active_recordassociations.rb的源代码翻出来给大家看看:
1,base.rb
def with_scope(method_scoping = {}, action = :merge, &block)
method_scoping = method_scoping.method_scoping if method_scoping.respond_to?(:method_scoping)
# Dup first and second level of hash (method and params).
method_scoping = method_scoping.inject({}) do |hash, (method, params)|
hash[method] = (params == true) ? params : params.dup
hash
end
method_scoping.assert_valid_keys([ :find, :create ])
if f = method_scoping[:find]
f.assert_valid_keys([ :conditions, :joins, :select, :include, :from, :offset, :limit, :order, :readonly, :lock ])
f[:readonly] = true if !f[:joins].blank? && !f.has_key?(:readonly)
end
# Merge scopings
if action == :merge && current_scoped_methods
method_scoping = current_scoped_methods.inject(method_scoping) do |hash, (method, params)|
case hash[method]
when Hash
if method == :find
(hash[method].keys + params.keys).uniq.each do |key|
merge = hash[method][key] && params[key] # merge if both scopes have the same key
if key == :conditions && merge
hash[method][key] = [params[key], hash[method][key]].collect{ |sql| "( %s )" % sanitize_sql(sql) }.join(" AND ")
elsif key == :include && merge
hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
else
hash[method][key] = hash[method][key] || params[key]
end
end
else
hash[method] = params.merge(hash[method])
end
else
hash[method] = params
end
hash
end
end
self.scoped_methods << method_scoping
begin
yield
ensure
self.scoped_methods.pop
end
end
def find_every(options)
records = scoped?(:find, :include) || options[:include] ?
find_with_associations(options) :
find_by_sql(construct_finder_sql(options))
records.each { |record| record.readonly! } if options[:readonly]
records
end
2,associations.rb
module ActiveRecord
module Associations
module ClassMethods
private
def find_with_associations(options = {})
catch :invalid_query do
join_dependency = JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
rows = select_all_rows(options, join_dependency)
return join_dependency.instantiate(rows)
end
[]
end
def select_all_rows(options, join_dependency)
connection.select_all(
construct_finder_sql_with_included_associations(options, join_dependency),
"#{name} Load Including Associations"
)
end
def construct_finder_sql_with_included_associations(options, join_dependency)
scope = scope(:find)
sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || table_name} "
sql << join_dependency.join_associations.collect{|join| join.association_join }.join
add_joins!(sql, options, scope)
add_conditions!(sql, options[:conditions], scope)
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
sql << "GROUP BY #{options[:group]} " if options[:group]
add_order!(sql, options[:order], scope)
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
add_lock!(sql, options, scope)
return sanitize_sql(sql)
end
class JoinDependency
class JoinAssociation < JoinBase
def association_join
join = case reflection.macro
when :has_and_belongs_to_many
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
table_alias_for(options[:join_table], aliased_join_table_name),
aliased_join_table_name,
options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key,
parent.aliased_table_name, reflection.active_record.primary_key] +
" LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
table_name_and_alias, aliased_table_name, klass.primary_key,
aliased_join_table_name, options[:association_foreign_key] || klass.table_name.classify.foreign_key
]
when :has_many, :has_one
...
when :belongs_to
...
else
...
join
end
end
end
调用流程是这样的:
ActiveRecord.find* -> with_scope -> find_every -> find_with_associations -> select_all_rows -> construct_finder_sql_with_included_associations -> association_join
如果提供:include选项,则调用find_with_associations,即eager loading,否则调用find_by_sql,为lazy loading
最后我们看到JoinAssociation的association_join方法使用LEFT OUTER JOIN来做关联查询,做eager loading
一切都弄清楚了吧?
BTW:对于open source项目,遇到问题时看看source code是很好的solution