Skip to content
This repository
Browse code

Added cascading eager loading that allows for queries like Author.fin…

…d(:all, :include=> { :posts=> :comments }), which will fetch all authors, their posts, and the comments belonging to those posts in a single query (using LEFT OUTER JOIN) #3913 [anna@wota.jp]

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@3769 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
  • Loading branch information...
commit 55854c4195177d2d5cbf0497c77e63b24cb76074 1 parent 30caefd
David Heinemeier Hansson dhh authored
29 activerecord/CHANGELOG
... ... @@ -1,5 +1,34 @@
1 1 *SVN*
2 2
  3 +* Added cascading eager loading that allows for queries like Author.find(:all, :include=> { :posts=> :comments }), which will fetch all authors, their posts, and the comments belonging to those posts in a single query (using LEFT OUTER JOIN) #3913 [anna@wota.jp]. Examples:
  4 +
  5 + # cascaded in two levels
  6 + >> Author.find(:all, :include=>{:posts=>:comments})
  7 + => authors
  8 + +- posts
  9 + +- comments
  10 +
  11 + # cascaded in two levels and normal association
  12 + >> Author.find(:all, :include=>[{:posts=>:comments}, :categorizations])
  13 + => authors
  14 + +- posts
  15 + +- comments
  16 + +- categorizations
  17 +
  18 + # cascaded in two levels with two has_many associations
  19 + >> Author.find(:all, :include=>{:posts=>[:comments, :categorizations]})
  20 + => authors
  21 + +- posts
  22 + +- comments
  23 + +- categorizations
  24 +
  25 + # cascaded in three levels
  26 + >> Company.find(:all, :include=>{:groups=>{:members=>{:favorites}}})
  27 + => companies
  28 + +- groups
  29 + +- members
  30 + +- favorites
  31 +
3 32 * Make counter cache work when replacing an association #3245 [eugenol@gmail.com]
4 33
5 34 * Make migrations verbose [Jamis Buck]
370 activerecord/lib/active_record/associations.rb
@@ -781,51 +781,15 @@ def association_constructor_method(constructor, reflection, association_proxy_cl
781 781 end
782 782
783 783 def count_with_associations(options = {})
784   - reflections = reflect_on_included_associations(options[:include])
785   - return count_by_sql(construct_counter_sql_with_included_associations(options, reflections))
  784 + join_dependency = JoinDependency.new(self, options[:include])
  785 + return count_by_sql(construct_counter_sql_with_included_associations(options, join_dependency))
786 786 end
787 787
788 788 def find_with_associations(options = {})
789   - reflections = reflect_on_included_associations(options[:include])
790   -
791   - guard_against_missing_reflections(reflections, options)
792   -
793   - schema_abbreviations = generate_schema_abbreviations(reflections)
794   - primary_key_table = generate_primary_key_table(reflections, schema_abbreviations)
795   -
796   - rows = select_all_rows(options, schema_abbreviations, reflections)
797   - records, records_in_order = { }, []
798   - primary_key = primary_key_table[table_name]
799   -
800   - for row in rows
801   - id = row[primary_key]
802   - records_in_order << (records[id] = instantiate(extract_record(schema_abbreviations, table_name, row))) unless records[id]
803   - record = records[id]
804   -
805   - reflections.each do |reflection|
806   - case reflection.macro
807   - when :has_many, :has_and_belongs_to_many
808   - collection = record.send(reflection.name)
809   - collection.loaded
810   -
811   - next unless row[primary_key_table[reflection.table_name]]
812   -
813   - association = reflection.klass.send(:instantiate, extract_record(schema_abbreviations, reflection.table_name, row))
814   - collection.target.push(association) unless collection.target.include?(association)
815   - when :has_one, :belongs_to
816   - next unless row[primary_key_table[reflection.table_name]]
817   -
818   - record.send(
819   - "set_#{reflection.name}_target",
820   - reflection.klass.send(:instantiate, extract_record(schema_abbreviations, reflection.table_name, row))
821   - )
822   - end
  789 + join_dependency = JoinDependency.new(self, options[:include])
  790 + rows = select_all_rows(options, join_dependency)
  791 + return join_dependency.instantiate(rows)
823 792 end
824   - end
825   -
826   - return records_in_order
827   - end
828   -
829 793
830 794 def configure_dependency_for_has_many(reflection)
831 795 if reflection.options[:dependent] && reflection.options[:exclusively_dependent]
@@ -939,16 +903,6 @@ def reflect_on_included_associations(associations)
939 903 [ associations ].flatten.collect { |association| reflect_on_association(association.to_s.intern) }
940 904 end
941 905
942   - def guard_against_missing_reflections(reflections, options)
943   - reflections.each do |r|
944   - raise(
945   - ConfigurationError,
946   - "Association was not found; perhaps you misspelled it? " +
947   - "You specified :include => :#{[options[:include]].flatten.join(', :')}"
948   - ) if r.nil?
949   - end
950   - end
951   -
952 906 def guard_against_unlimitable_reflections(reflections, options)
953 907 if (options[:offset] || options[:limit]) && !using_limitable_reflections?(reflections)
954 908 raise(
@@ -958,42 +912,14 @@ def guard_against_unlimitable_reflections(reflections, options)
958 912 end
959 913 end
960 914
961   - def generate_schema_abbreviations(reflections)
962   - schema = [ [ table_name, column_names ] ]
963   - schema += reflections.collect { |r| [ r.table_name, r.klass.column_names ] }
964   -
965   - schema_abbreviations = {}
966   - schema.each_with_index do |table_and_columns, i|
967   - table, columns = table_and_columns
968   - columns.each_with_index { |column, j| schema_abbreviations["t#{i}_r#{j}"] = [ table, column ] }
969   - end
970   -
971   - return schema_abbreviations
972   - end
973   -
974   - def generate_primary_key_table(reflections, schema_abbreviations)
975   - primary_key_lookup_table = {}
976   - primary_key_lookup_table[table_name] =
977   - schema_abbreviations.find { |cn, tc| tc == [ table_name, primary_key ] }.first
978   -
979   - reflections.collect do |reflection|
980   - primary_key_lookup_table[reflection.klass.table_name] = schema_abbreviations.find { |cn, tc|
981   - tc == [ reflection.klass.table_name, reflection.klass.primary_key ]
982   - }.first
983   - end
984   -
985   - return primary_key_lookup_table
986   - end
987   -
988   -
989   - def select_all_rows(options, schema_abbreviations, reflections)
  915 + def select_all_rows(options, join_dependency)
990 916 connection.select_all(
991   - construct_finder_sql_with_included_associations(options, schema_abbreviations, reflections),
  917 + construct_finder_sql_with_included_associations(options, join_dependency),
992 918 "#{name} Load Including Associations"
993 919 )
994 920 end
995 921
996   - def construct_counter_sql_with_included_associations(options, reflections)
  922 + def construct_counter_sql_with_included_associations(options, join_dependency)
997 923 sql = "SELECT COUNT(DISTINCT #{table_name}.#{primary_key})"
998 924
999 925 # A (slower) workaround if we're using a backend, like sqlite, that doesn't support COUNT DISTINCT.
@@ -1002,14 +928,14 @@ def construct_counter_sql_with_included_associations(options, reflections)
1002 928 end
1003 929
1004 930 sql << " FROM #{table_name} "
1005   - sql << reflections.collect { |reflection| association_join(reflection) }.to_s
  931 + sql << join_dependency.join_associations.collect{|join| join.association_join }.join
1006 932 sql << "#{options[:joins]} " if options[:joins]
1007 933
1008 934 add_conditions!(sql, options[:conditions])
1009   - add_sti_conditions!(sql, reflections)
1010   - add_limited_ids_condition!(sql, options, reflections) if !using_limitable_reflections?(reflections) && options[:limit]
  935 + add_sti_conditions!(sql, join_dependency)
  936 + add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit]
1011 937
1012   - add_limit!(sql, options) if using_limitable_reflections?(reflections)
  938 + add_limit!(sql, options) if using_limitable_reflections?(join_dependency.reflections)
1013 939
1014 940 if !Base.connection.supports_count_distinct?
1015 941 sql << ")"
@@ -1018,43 +944,43 @@ def construct_counter_sql_with_included_associations(options, reflections)
1018 944 return sanitize_sql(sql)
1019 945 end
1020 946
1021   - def construct_finder_sql_with_included_associations(options, schema_abbreviations, reflections)
1022   - sql = "SELECT #{column_aliases(schema_abbreviations)} FROM #{options[:from] || table_name} "
1023   - sql << reflections.collect { |reflection| association_join(reflection) }.to_s
  947 + def construct_finder_sql_with_included_associations(options, join_dependency)
  948 + sql = "SELECT #{column_aliases(join_dependency)} FROM #{options[:from] || table_name} "
  949 + sql << join_dependency.join_associations.collect{|join| join.association_join }.join
1024 950 sql << "#{options[:joins]} " if options[:joins]
1025 951
1026 952 add_conditions!(sql, options[:conditions])
1027   - add_sti_conditions!(sql, reflections)
1028   - add_limited_ids_condition!(sql, options, reflections) if !using_limitable_reflections?(reflections) && options[:limit]
  953 + add_sti_conditions!(sql, join_dependency)
  954 + add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && options[:limit]
1029 955
1030 956 sql << "ORDER BY #{options[:order]} " if options[:order]
1031 957
1032   - add_limit!(sql, options) if using_limitable_reflections?(reflections)
  958 + add_limit!(sql, options) if using_limitable_reflections?(join_dependency.reflections)
1033 959
1034 960 return sanitize_sql(sql)
1035 961 end
1036 962
1037   - def add_limited_ids_condition!(sql, options, reflections)
1038   - unless (id_list = select_limited_ids_list(options, reflections)).empty?
  963 + def add_limited_ids_condition!(sql, options, join_dependency)
  964 + unless (id_list = select_limited_ids_list(options, join_dependency)).empty?
1039 965 sql << "#{condition_word(sql)} #{table_name}.#{primary_key} IN (#{id_list}) "
1040 966 end
1041 967 end
1042 968
1043   - def select_limited_ids_list(options, reflections)
  969 + def select_limited_ids_list(options, join_dependency)
1044 970 connection.select_values(
1045   - construct_finder_sql_for_association_limiting(options, reflections),
  971 + construct_finder_sql_for_association_limiting(options, join_dependency),
1046 972 "#{name} Load IDs For Limited Eager Loading"
1047 973 ).collect { |id| connection.quote(id) }.join(", ")
1048 974 end
1049 975
1050   - def construct_finder_sql_for_association_limiting(options, reflections)
  976 + def construct_finder_sql_for_association_limiting(options, join_dependency)
1051 977 #sql = "SELECT DISTINCT #{table_name}.#{primary_key} FROM #{table_name} "
1052 978 sql = "SELECT "
1053 979 sql << "DISTINCT #{table_name}." if include_eager_conditions?(options) || include_eager_order?(options)
1054 980 sql << "#{primary_key} FROM #{table_name} "
1055 981
1056 982 if include_eager_conditions?(options) || include_eager_order?(options)
1057   - sql << reflections.collect { |reflection| association_join(reflection) }.to_s
  983 + sql << join_dependency.join_associations.collect{|join| join.association_join }.join
1058 984 sql << "#{options[:joins]} " if options[:joins]
1059 985 end
1060 986
@@ -1085,9 +1011,34 @@ def using_limitable_reflections?(reflections)
1085 1011 reflections.reject { |r| [ :belongs_to, :has_one ].include?(r.macro) }.length.zero?
1086 1012 end
1087 1013
1088   - def add_sti_conditions!(sql, reflections)
  1014 + def join_depended_type_condition (klass, join_dependency)
  1015 + aliased_table_name = join_dependency.aliased_table_names_for(klass.table_name).last || klass.table_name
  1016 + quoted_inheritance_column = connection.quote_column_name(klass.inheritance_column)
  1017 + type_condition = klass.subclasses.inject(sti_condition(klass, aliased_table_name, quoted_inheritance_column)) do |condition, subclass|
  1018 + condition << " OR #{sti_condition subclass, aliased_table_name, quoted_inheritance_column}"
  1019 + end
  1020 +
  1021 + " (#{type_condition}) "
  1022 + end
  1023 +
  1024 + def sti_condition(klass, table_name, inheritance_column)
  1025 + "(#{table_name}.#{inheritance_column} = '#{klass.name.demodulize}' OR #{table_name}.#{inheritance_column} IS NULL)"
  1026 + end
  1027 +
  1028 + #def join_depended_type_condition (klass, join_dependency)
  1029 + # aliased_table_name = join_dependency.aliased_table_names_for(klass.table_name).first || klass.table_name
  1030 + # quoted_inheritance_column = connection.quote_column_name(klass.inheritance_column)
  1031 + # type_condition = klass.subclasses.inject("#{aliased_table_name}.#{quoted_inheritance_column} = '#{klass.name.demodulize}' ") do |condition, subclass|
  1032 + # condition << "OR #{aliased_table_name}.#{quoted_inheritance_column} = '#{subclass.name.demodulize}' "
  1033 + # end
  1034 + #
  1035 + # " (#{type_condition}) "
  1036 + #end
  1037 +
  1038 + def add_sti_conditions!(sql, join_dependency)
  1039 + reflections = join_dependency.reflections
1089 1040 sti_conditions = reflections.collect do |reflection|
1090   - reflection.klass.send(:type_condition) unless reflection.klass.descends_from_active_record?
  1041 + join_depended_type_condition(reflection.klass, join_dependency) unless reflection.klass.descends_from_active_record?
1091 1042 end.compact
1092 1043
1093 1044 unless sti_conditions.empty?
@@ -1095,30 +1046,9 @@ def add_sti_conditions!(sql, reflections)
1095 1046 end
1096 1047 end
1097 1048
1098   - def column_aliases(schema_abbreviations)
1099   - schema_abbreviations.collect { |cn, tc| "#{tc[0]}.#{connection.quote_column_name tc[1]} AS #{cn}" }.join(", ")
1100   - end
1101   -
1102   - def association_join(reflection)
1103   - case reflection.macro
1104   - when :has_and_belongs_to_many
1105   - " LEFT OUTER JOIN #{reflection.options[:join_table]} ON " +
1106   - "#{reflection.options[:join_table]}.#{reflection.options[:foreign_key] || table_name.classify.foreign_key} = " +
1107   - "#{table_name}.#{primary_key} " +
1108   - " LEFT OUTER JOIN #{reflection.klass.table_name} ON " +
1109   - "#{reflection.options[:join_table]}.#{reflection.options[:association_foreign_key] || reflection.klass.table_name.classify.foreign_key} = " +
1110   - "#{reflection.klass.table_name}.#{reflection.klass.primary_key} "
1111   - when :has_many, :has_one
1112   - " LEFT OUTER JOIN #{reflection.klass.table_name} ON " +
1113   - "#{reflection.klass.table_name}.#{reflection.options[:foreign_key] || table_name.classify.foreign_key} = " +
1114   - "#{table_name}.#{primary_key} "
1115   - when :belongs_to
1116   - " LEFT OUTER JOIN #{reflection.klass.table_name} ON " +
1117   - "#{reflection.klass.table_name}.#{reflection.klass.primary_key} = " +
1118   - "#{table_name}.#{reflection.options[:foreign_key] || reflection.klass.table_name.classify.foreign_key} "
1119   - else
1120   - ""
1121   - end
  1049 + def column_aliases(join_dependency)
  1050 + join_dependency.joins.collect{|join| join.column_names_with_alias.collect{|column_name, aliased_name|
  1051 + "#{join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}"}}.flatten.join(", ")
1122 1052 end
1123 1053
1124 1054 def add_association_callbacks(association_name, options)
@@ -1133,15 +1063,6 @@ def add_association_callbacks(association_name, options)
1133 1063 end
1134 1064 end
1135 1065
1136   - def extract_record(schema_abbreviations, table_name, row)
1137   - record = {}
1138   - row.each do |column, value|
1139   - prefix, column_name = schema_abbreviations[column]
1140   - record[column_name] = value if prefix == table_name
1141   - end
1142   - return record
1143   - end
1144   -
1145 1066 def condition_word(sql)
1146 1067 sql =~ /where/i ? " AND " : "WHERE "
1147 1068 end
@@ -1155,6 +1076,189 @@ def create_extension_module(association_id, extension)
1155 1076
1156 1077 extension_module_name.constantize
1157 1078 end
  1079 +
  1080 + class JoinDependency
  1081 + attr_reader :joins, :reflections
  1082 +
  1083 + def initialize(base, associations)
  1084 + @joins = [JoinBase.new(base)]
  1085 + @associations = associations
  1086 + @reflections = []
  1087 + @base_records_hash = {}
  1088 + @base_records_in_order = []
  1089 + build(associations)
  1090 + end
  1091 +
  1092 + def join_associations
  1093 + @joins[1..-1].to_a
  1094 + end
  1095 +
  1096 + def join_base
  1097 + @joins[0]
  1098 + end
  1099 +
  1100 + def instantiate(rows)
  1101 + rows.each_with_index do |row, i|
  1102 + primary_id = join_base.record_id(row)
  1103 + unless @base_records_hash[primary_id]
  1104 + @base_records_in_order << (@base_records_hash[primary_id] = join_base.instantiate(row))
  1105 + end
  1106 + construct(@base_records_hash[primary_id], @associations, join_associations.dup, row)
  1107 + end
  1108 + return @base_records_in_order
  1109 + end
  1110 +
  1111 + def aliased_table_names_for(table_name)
  1112 + joins.select{|join| join.table_name == table_name }.collect{|join| join.aliased_table_name}
  1113 + end
  1114 +
  1115 + protected
  1116 + def build(associations, parent = nil)
  1117 + parent ||= @joins.last
  1118 + case associations
  1119 + when Symbol, String
  1120 + reflection = parent.reflections[associations.to_s.intern] or
  1121 + raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?"
  1122 + @reflections << reflection
  1123 + @joins << JoinAssociation.new(reflection, self, parent)
  1124 + when Array
  1125 + associations.each do |association|
  1126 + build(association, parent)
  1127 + end
  1128 + when Hash
  1129 + associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
  1130 + build(name, parent)
  1131 + build(associations[name])
  1132 + end
  1133 + else
  1134 + raise ConfigurationError, associations.inspect
  1135 + end
  1136 + end
  1137 +
  1138 + def construct(parent, associations, joins, row)
  1139 + case associations
  1140 + when Symbol, String
  1141 + while (join = joins.shift).reflection.name.to_s != associations.to_s
  1142 + raise ConfigurationError, "Not Enough Associations" if joins.empty?
  1143 + end
  1144 + construct_association(parent, join, row)
  1145 + when Array
  1146 + associations.each do |association|
  1147 + construct(parent, association, joins, row)
  1148 + end
  1149 + when Hash
  1150 + associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name|
  1151 + association = construct_association(parent, joins.shift, row)
  1152 + construct(association, associations[name], joins, row) if association
  1153 + end
  1154 + else
  1155 + raise ConfigurationError, associations.inspect
  1156 + end
  1157 + end
  1158 +
  1159 + def construct_association(record, join, row)
  1160 + case join.reflection.macro
  1161 + when :has_many, :has_and_belongs_to_many
  1162 + collection = record.send(join.reflection.name)
  1163 + collection.loaded
  1164 +
  1165 + return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
  1166 + association = join.instantiate(row)
  1167 + collection.target.push(association) unless collection.target.include?(association)
  1168 + when :has_one, :belongs_to
  1169 + return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil?
  1170 + association = join.instantiate(row)
  1171 + record.send("set_#{join.reflection.name}_target", association)
  1172 + else
  1173 + raise ConfigurationError, "unknown macro: #{join.reflection.macro}"
  1174 + end
  1175 + return association
  1176 + end
  1177 +
  1178 + class JoinBase
  1179 + attr_reader :active_record
  1180 + delegate :table_name, :column_names, :primary_key, :reflections, :to=>:active_record
  1181 +
  1182 + def initialize(active_record)
  1183 + @active_record = active_record
  1184 + @cached_record = {}
  1185 + end
  1186 +
  1187 + def aliased_prefix
  1188 + "t0"
  1189 + end
  1190 +
  1191 + def aliased_primary_key
  1192 + "#{ aliased_prefix }_r0"
  1193 + end
  1194 +
  1195 + def aliased_table_name
  1196 + active_record.table_name
  1197 + end
  1198 +
  1199 + def column_names_with_alias
  1200 + unless @column_names_with_alias
  1201 + @column_names_with_alias = []
  1202 + ([primary_key] + (column_names - [primary_key])).each_with_index do |column_name, i|
  1203 + @column_names_with_alias << [column_name, "#{ aliased_prefix }_r#{ i }"]
  1204 + end
  1205 + end
  1206 + return @column_names_with_alias
  1207 + end
  1208 +
  1209 + def extract_record(row)
  1210 + column_names_with_alias.inject({}){|record, (cn, an)| record[cn] = row[an]; record}
  1211 + end
  1212 +
  1213 + def record_id(row)
  1214 + row[aliased_primary_key]
  1215 + end
  1216 +
  1217 + def instantiate(row)
  1218 + @cached_record[record_id(row)] ||= active_record.instantiate(extract_record(row))
  1219 + end
  1220 + end
  1221 +
  1222 + class JoinAssociation < JoinBase
  1223 + attr_reader :reflection, :parent, :aliased_table_name, :aliased_prefix
  1224 + delegate :options, :klass, :to=>:reflection
  1225 +
  1226 + def initialize(reflection, join_dependency, parent = nil)
  1227 + super(reflection.klass)
  1228 + @parent = parent
  1229 + @reflection = reflection
  1230 + @aliased_prefix = "t#{ join_dependency.joins.size }"
  1231 + @aliased_table_name = join_dependency.aliased_table_names_for(table_name).empty? ? table_name : @aliased_prefix
  1232 + end
  1233 +
  1234 + def association_join
  1235 + case reflection.macro
  1236 + when :has_and_belongs_to_many
  1237 + join_table_name =
  1238 + " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
  1239 + options[:join_table], options[:join_table],
  1240 + options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key,
  1241 + reflection.active_record.table_name, reflection.active_record.primary_key] +
  1242 + " LEFT OUTER JOIN %s ON %s.%s = %s.%s " % [
  1243 + aliased_table_name, aliased_table_name, klass.primary_key,
  1244 + options[:join_table], options[:association_foreign_key] || klass.table_name.classify.foreign_key
  1245 + ]
  1246 + when :has_many, :has_one
  1247 + " LEFT OUTER JOIN %s AS %s ON %s.%s = %s.%s " % [table_name, aliased_table_name,
  1248 + aliased_table_name, options[:foreign_key] || reflection.active_record.to_s.classify.foreign_key,
  1249 + parent.aliased_table_name, parent.primary_key
  1250 + ]
  1251 + when :belongs_to
  1252 + " LEFT OUTER JOIN %s AS %s ON %s.%s = %s.%s " % [table_name, aliased_table_name,
  1253 + aliased_table_name, reflection.klass.primary_key,
  1254 + parent.aliased_table_name, options[:foreign_key] || reflection.klass.to_s.classify.foreign_key
  1255 + ]
  1256 + else
  1257 + ""
  1258 + end
  1259 + end
  1260 + end
  1261 + end
1158 1262 end
1159 1263 end
1160 1264 end
78 activerecord/test/associations_cascaded_eager_loading_test.rb
... ... @@ -0,0 +1,78 @@
  1 +require 'abstract_unit'
  2 +require 'active_record/acts/list'
  3 +require 'fixtures/post'
  4 +require 'fixtures/comment'
  5 +require 'fixtures/author'
  6 +require 'fixtures/category'
  7 +require 'fixtures/categorization'
  8 +require 'fixtures/mixin'
  9 +require 'fixtures/company'
  10 +require 'fixtures/topic'
  11 +require 'fixtures/reply'
  12 +
  13 +class CascadedEagerLoadingTest < Test::Unit::TestCase
  14 + fixtures :authors, :mixins, :companies, :posts, :categorizations, :topics
  15 +
  16 + def test_eager_association_loading_with_cascaded_two_levels
  17 + authors = Author.find(:all, :include=>{:posts=>:comments}, :order=>"authors.id")
  18 + assert_equal 2, authors.size
  19 + assert_equal 5, authors[0].posts.size
  20 + assert_equal 1, authors[1].posts.size
  21 + assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
  22 + end
  23 +
  24 + def test_eager_association_loading_with_cascaded_two_levels_and_one_level
  25 + authors = Author.find(:all, :include=>[{:posts=>:comments}, :categorizations], :order=>"authors.id")
  26 + assert_equal 2, authors.size
  27 + assert_equal 5, authors[0].posts.size
  28 + assert_equal 1, authors[1].posts.size
  29 + assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
  30 + assert_equal 1, authors[0].categorizations.size
  31 + assert_equal 1, authors[1].categorizations.size
  32 + end
  33 +
  34 + def test_eager_association_loading_with_cascaded_two_levels_with_two_has_many_associations
  35 + authors = Author.find(:all, :include=>{:posts=>[:comments, :categorizations]}, :order=>"authors.id")
  36 + assert_equal 2, authors.size
  37 + assert_equal 5, authors[0].posts.size
  38 + assert_equal 1, authors[1].posts.size
  39 + assert_equal 9, authors[0].posts.collect{|post| post.comments.size }.inject(0){|sum,i| sum+i}
  40 + end
  41 +
  42 + def test_eager_association_loading_with_cascaded_two_levels_and_self_table_reference
  43 + authors = Author.find(:all, :include=>{:posts=>[:comments, :author]}, :order=>"authors.id")
  44 + assert_equal 2, authors.size
  45 + assert_equal 5, authors[0].posts.size
  46 + assert_equal authors(:david).name, authors[0].name
  47 + assert_equal [authors(:david).name], authors[0].posts.collect{|post| post.author.name}.uniq
  48 + end
  49 +
  50 + def test_eager_association_loading_with_cascaded_two_levels_with_condition
  51 + authors = Author.find(:all, :include=>{:posts=>:comments}, :conditions=>"authors.id=1", :order=>"authors.id")
  52 + assert_equal 1, authors.size
  53 + assert_equal 5, authors[0].posts.size
  54 + end
  55 +
  56 + def test_eager_association_loading_with_acts_as_tree
  57 + roots = TreeMixin.find(:all, :include=>"children", :conditions=>"mixins.parent_id IS NULL", :order=>"mixins.id")
  58 + assert_equal [mixins(:tree_1), mixins(:tree2_1), mixins(:tree3_1)], roots
  59 + assert_equal 2, roots[0].children.size
  60 + assert_equal 0, roots[1].children.size
  61 + assert_equal 0, roots[2].children.size
  62 + end
  63 +
  64 + def test_eager_association_loading_with_cascaded_three_levels_by_ping_pong
  65 + firms = Firm.find(:all, :include=>{:account=>{:firm=>:account}}, :order=>"companies.id")
  66 + assert_equal 2, firms.size
  67 + assert_equal firms.first.account, firms.first.account.firm.account
  68 + assert_equal companies(:first_firm).account, firms.first.account.firm.account
  69 + assert_equal companies(:first_firm).account.firm.account, firms.first.account.firm.account
  70 + end
  71 +
  72 + def test_eager_association_loading_with_sti
  73 + topics = Topic.find(:all, :include => :replies, :order => 'topics.id')
  74 + assert_equal [topics(:first), topics(:second)], topics
  75 + assert_equal 1, topics[0].replies.size
  76 + assert_equal 0, topics[1].replies.size
  77 + end
  78 +end
10 activerecord/test/associations_go_eager_test.rb
@@ -92,15 +92,15 @@ def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_cond
92 92 end
93 93
94 94 def test_eager_association_loading_with_belongs_to_and_limit_and_multiple_associations
95   - posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1)
  95 + posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :order => 'posts.id')
96 96 assert_equal 1, posts.length
97   - assert_equal [4], posts.collect { |p| p.id }
  97 + assert_equal [3], posts.collect { |p| p.id }
98 98 end
99 99
100 100 def test_eager_association_loading_with_belongs_to_and_limit_and_offset_and_multiple_associations
101   - posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :offset => 1)
102   - assert_equal 0, posts.length
103   - assert_equal [], posts
  101 + posts = Post.find(:all, :include => [:author, :very_special_comment], :limit => 1, :offset => 1, :order => 'posts.id')
  102 + assert_equal 1, posts.length
  103 + assert_equal [4], posts.collect { |p| p.id }
104 104 end
105 105
106 106 def test_eager_with_has_many_through

0 comments on commit 55854c4

Please sign in to comment.
Something went wrong with that request. Please try again.