| 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 |
|---|
| 823 | | end |
|---|
| 824 | | end |
|---|
| 825 | | |
|---|
| 826 | | return records_in_order |
|---|
| 827 | | end |
|---|
| 828 | | |
|---|
| | 789 | join_dependency = JoinDependency.new(self, options[:include]) |
|---|
| | 790 | rows = select_all_rows(options, join_dependency) |
|---|
| | 791 | return join_dependency.instantiate(rows) |
|---|
| | 792 | end |
|---|
| 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) |
|---|
| 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 |
|---|
| 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(", ") |
|---|
| | 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 |
|---|