| 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 |
class IncludedAssocAttrSelector |
|---|
| 33 |
instance_methods.each { |m| undef_method m unless m =~ /^(__|hash$)/ } |
|---|
| 34 |
attr_reader :_association, :_attrs |
|---|
| 35 |
|
|---|
| 36 |
def initialize(association, attrs, cont) |
|---|
| 37 |
@_association = association |
|---|
| 38 |
@_attrs = attrs.map(&:to_s) |
|---|
| 39 |
@cont = cont |
|---|
| 40 |
end |
|---|
| 41 |
|
|---|
| 42 |
def to_s |
|---|
| 43 |
":#{@_association}#{@_attrs.inspect}" |
|---|
| 44 |
end |
|---|
| 45 |
alias :inspect :to_s |
|---|
| 46 |
|
|---|
| 47 |
def method_missing(symbol) |
|---|
| 48 |
@cont.call |
|---|
| 49 |
end |
|---|
| 50 |
end |
|---|
| 51 |
|
|---|
| 52 |
Symbol.class_eval do |
|---|
| 53 |
alias_method :orig_sq, :[] if method_defined?(:[]) |
|---|
| 54 |
def [](*attrs) |
|---|
| 55 |
callcc { |cont| return IncludedAssocAttrSelector.new(self, attrs, cont) } |
|---|
| 56 |
Symbol.method_defined?(:orig_sq) ? orig_sq(*attrs) : method_missing(:[], *attrs) |
|---|
| 57 |
end |
|---|
| 58 |
end |
|---|
| 59 |
|
|---|
| 60 |
class ActiveRecord::Base |
|---|
| 61 |
|
|---|
| 62 |
|
|---|
| 63 |
|
|---|
| 64 |
def self.construct_finder_sql_with_included_associations(options, join_dependency) |
|---|
| 65 |
scope = scope(:find) |
|---|
| 66 |
base_select = options[:select] || scope && scope[:select] || "#{table_name}.*" |
|---|
| 67 |
eager_select = eager_select(join_dependency) |
|---|
| 68 |
base_select += ', ' unless base_select.empty? || eager_select.empty? |
|---|
| 69 |
sql = "SELECT #{base_select + eager_select} FROM #{(scope && scope[:from]) || options[:from] || table_name} " |
|---|
| 70 |
sql << join_dependency.joins.map { |join| join.association_join }.join |
|---|
| 71 |
|
|---|
| 72 |
add_joins!(sql, options, scope) |
|---|
| 73 |
add_conditions!(sql, options[:conditions], scope) |
|---|
| 74 |
add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit]) |
|---|
| 75 |
|
|---|
| 76 |
sql << "GROUP BY #{options[:group]} " if options[:group] |
|---|
| 77 |
|
|---|
| 78 |
add_order!(sql, options[:order], scope) |
|---|
| 79 |
add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections) |
|---|
| 80 |
add_lock!(sql, options, scope) |
|---|
| 81 |
|
|---|
| 82 |
return sanitize_sql(sql) |
|---|
| 83 |
end |
|---|
| 84 |
|
|---|
| 85 |
|
|---|
| 86 |
|
|---|
| 87 |
def self.eager_select(join_dependency) |
|---|
| 88 |
join_dependency.joins.map do |join| |
|---|
| 89 |
join.column_names_with_alias.map do |column_name, aliased_name| |
|---|
| 90 |
"#{join.aliased_table_name}.#{connection.quote_column_name column_name} AS #{aliased_name}" |
|---|
| 91 |
end |
|---|
| 92 |
end.flatten.join(', ') |
|---|
| 93 |
end |
|---|
| 94 |
end |
|---|
| 95 |
|
|---|
| 96 |
class ActiveRecord::Associations::ClassMethods::JoinDependency |
|---|
| 97 |
|
|---|
| 98 |
|
|---|
| 99 |
|
|---|
| 100 |
|
|---|
| 101 |
|
|---|
| 102 |
attr_reader :table_joins |
|---|
| 103 |
|
|---|
| 104 |
def initialize(base, associations, joins) |
|---|
| 105 |
@base = base |
|---|
| 106 |
@base_id = base.primary_key |
|---|
| 107 |
@joins = [] |
|---|
| 108 |
@join_base = JoinBase.new(base) |
|---|
| 109 |
@table_joins = joins |
|---|
| 110 |
@associations = associations |
|---|
| 111 |
@reflections = [] |
|---|
| 112 |
@base_records_hash = {} |
|---|
| 113 |
@base_records_in_order = [] |
|---|
| 114 |
@table_aliases = Hash.new { |aliases, table| aliases[table] = 0 } |
|---|
| 115 |
@table_aliases[base.table_name] = 1 |
|---|
| 116 |
build(associations, @join_base) |
|---|
| 117 |
end |
|---|
| 118 |
|
|---|
| 119 |
|
|---|
| 120 |
|
|---|
| 121 |
def instantiate(rows) |
|---|
| 122 |
j = joins.map { |j| j.reflection.name }.join(', ') |
|---|
| 123 |
rows.each do |row| |
|---|
| 124 |
primary_id = row[@base_id] |
|---|
| 125 |
unless @base_records_hash[primary_id] |
|---|
| 126 |
base_attrs = row.reject { |attr, value| attr[0] == ?_ } |
|---|
| 127 |
@base_records_in_order << (@base_records_hash[primary_id] = @base.send(:instantiate, base_attrs)) |
|---|
| 128 |
end |
|---|
| 129 |
@join_index = -1 |
|---|
| 130 |
construct(@base_records_hash[primary_id], @associations, joins, row) |
|---|
| 131 |
end |
|---|
| 132 |
remove_duplicate_results!(@base, @base_records_in_order, @associations) |
|---|
| 133 |
return @base_records_in_order |
|---|
| 134 |
end |
|---|
| 135 |
|
|---|
| 136 |
def join_associations() @joins end |
|---|
| 137 |
def join_base() @join_base end |
|---|
| 138 |
|
|---|
| 139 |
protected |
|---|
| 140 |
|
|---|
| 141 |
|
|---|
| 142 |
|
|---|
| 143 |
def build(associations, parent) |
|---|
| 144 |
case associations |
|---|
| 145 |
when Symbol, String, IncludedAssocAttrSelector |
|---|
| 146 |
attrs = nil |
|---|
| 147 |
if IncludedAssocAttrSelector === associations |
|---|
| 148 |
attrs = associations._attrs |
|---|
| 149 |
associations = associations._association |
|---|
| 150 |
end |
|---|
| 151 |
unless reflection = parent.reflections[associations.to_s.intern] |
|---|
| 152 |
raise ActiveRecord::ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?" |
|---|
| 153 |
end |
|---|
| 154 |
@reflections << reflection |
|---|
| 155 |
@joins << (join_assoc = build_join_association(reflection, parent, attrs)) |
|---|
| 156 |
join_assoc |
|---|
| 157 |
when Array |
|---|
| 158 |
associations.each do |association| |
|---|
| 159 |
build(association, parent) |
|---|
| 160 |
end |
|---|
| 161 |
when Hash |
|---|
| 162 |
associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name| |
|---|
| 163 |
build(associations[name], build(name, parent)) |
|---|
| 164 |
end |
|---|
| 165 |
else |
|---|
| 166 |
raise ActiveRecord::ConfigurationError, associations.inspect |
|---|
| 167 |
end |
|---|
| 168 |
end |
|---|
| 169 |
|
|---|
| 170 |
def build_join_association(reflection, parent, attrs) |
|---|
| 171 |
JoinAssociation.new(reflection, self, parent, attrs) |
|---|
| 172 |
end |
|---|
| 173 |
|
|---|
| 174 |
|
|---|
| 175 |
|
|---|
| 176 |
|
|---|
| 177 |
def construct(parent, associations, joins, row) |
|---|
| 178 |
|
|---|
| 179 |
case associations |
|---|
| 180 |
when Symbol, String, IncludedAssocAttrSelector |
|---|
| 181 |
associations = associations._association if IncludedAssocAttrSelector === associations |
|---|
| 182 |
while (join = joins[@join_index += 1]).reflection.name.to_s != associations.to_s |
|---|
| 183 |
raise ActiveRecord::ConfigurationError, "Not Enough Associations" if join.nil? |
|---|
| 184 |
end |
|---|
| 185 |
construct_association(parent, join, row) if parent |
|---|
| 186 |
when Array |
|---|
| 187 |
associations.each do |association| |
|---|
| 188 |
construct(parent, association, joins, row) |
|---|
| 189 |
end |
|---|
| 190 |
when Hash |
|---|
| 191 |
associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name| |
|---|
| 192 |
association = construct_association(parent, joins[@join_index+=1], row) if parent |
|---|
| 193 |
construct(association, associations[name], joins, row) |
|---|
| 194 |
end |
|---|
| 195 |
else |
|---|
| 196 |
raise ActiveRecord::ConfigurationError, associations.inspect |
|---|
| 197 |
end |
|---|
| 198 |
end |
|---|
| 199 |
|
|---|
| 200 |
|
|---|
| 201 |
|
|---|
| 202 |
def remove_duplicate_results!(base, records, associations) |
|---|
| 203 |
case associations |
|---|
| 204 |
when Symbol, String, IncludedAssocAttrSelector |
|---|
| 205 |
associations = associations._association if IncludedAssocAttrSelector === associations |
|---|
| 206 |
reflection = base.reflections[associations] |
|---|
| 207 |
if reflection && [:has_many, :has_and_belongs_to_many].include?(reflection.macro) |
|---|
| 208 |
records.each { |record| record.send(reflection.name).target.uniq! } |
|---|
| 209 |
end |
|---|
| 210 |
when Array |
|---|
| 211 |
associations.each do |association| |
|---|
| 212 |
remove_duplicate_results!(base, records, association) |
|---|
| 213 |
end |
|---|
| 214 |
when Hash |
|---|
| 215 |
associations.keys.each do |name| |
|---|
| 216 |
name = name._association if IncludedAssocAttrSelector === name |
|---|
| 217 |
reflection = base.reflections[name] |
|---|
| 218 |
is_collection = [:has_many, :has_and_belongs_to_many].include?(reflection.macro) |
|---|
| 219 |
|
|---|
| 220 |
parent_records = records.map do |record| |
|---|
| 221 |
next unless record.send(reflection.name) |
|---|
| 222 |
is_collection ? record.send(reflection.name).target.uniq! : record.send(reflection.name) |
|---|
| 223 |
end.flatten.compact |
|---|
| 224 |
|
|---|
| 225 |
remove_duplicate_results!(reflection.class_name.constantize, parent_records, associations[name]) unless parent_records.empty? |
|---|
| 226 |
end |
|---|
| 227 |
end |
|---|
| 228 |
end |
|---|
| 229 |
|
|---|
| 230 |
|
|---|
| 231 |
|
|---|
| 232 |
def construct_association(record, join, row) |
|---|
| 233 |
return nil if join.selected_attrs.empty? |
|---|
| 234 |
case join.reflection.macro |
|---|
| 235 |
when :has_many, :has_and_belongs_to_many |
|---|
| 236 |
collection = record.send(join.reflection.name) |
|---|
| 237 |
collection.loaded |
|---|
| 238 |
return nil if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil? |
|---|
| 239 |
association = join.instantiate(row) |
|---|
| 240 |
collection.target.push(association) unless collection.target.include?(association) |
|---|
| 241 |
when :has_one |
|---|
| 242 |
return if record.id.to_s != join.parent.record_id(row).to_s |
|---|
| 243 |
association = join.instantiate(row) unless row[join.aliased_primary_key].nil? |
|---|
| 244 |
record.send("set_#{join.reflection.name}_target", association) |
|---|
| 245 |
when :belongs_to |
|---|
| 246 |
return if record.id.to_s != join.parent.record_id(row).to_s or row[join.aliased_primary_key].nil? |
|---|
| 247 |
association = join.instantiate(row) |
|---|
| 248 |
record.send("set_#{join.reflection.name}_target", association) |
|---|
| 249 |
else |
|---|
| 250 |
raise ActiveRecord::ConfigurationError, "unknown macro: #{join.reflection.macro}" |
|---|
| 251 |
end |
|---|
| 252 |
return association |
|---|
| 253 |
end |
|---|
| 254 |
|
|---|
| 255 |
|
|---|
| 256 |
|
|---|
| 257 |
class JoinBase |
|---|
| 258 |
def aliased_prefix |
|---|
| 259 |
aliased_table_name |
|---|
| 260 |
end |
|---|
| 261 |
|
|---|
| 262 |
def aliased_primary_key |
|---|
| 263 |
active_record.primary_key |
|---|
| 264 |
end |
|---|
| 265 |
end |
|---|
| 266 |
|
|---|
| 267 |
class JoinAssociation |
|---|
| 268 |
|
|---|
| 269 |
|
|---|
| 270 |
|
|---|
| 271 |
|
|---|
| 272 |
attr_reader :selected_attrs |
|---|
| 273 |
|
|---|
| 274 |
def initialize(reflection, join_dependency, parent, selected_attrs = nil) |
|---|
| 275 |
reflection.check_validity! |
|---|
| 276 |
if reflection.options[:polymorphic] |
|---|
| 277 |
raise EagerLoadPolymorphicError.new(reflection) |
|---|
| 278 |
end |
|---|
| 279 |
|
|---|
| 280 |
super(reflection.klass) |
|---|
| 281 |
@selected_attrs = selected_attrs || column_names |
|---|
| 282 |
@parent = parent |
|---|
| 283 |
@reflection = reflection |
|---|
| 284 |
@aliased_prefix = "t#{ join_dependency.joins.size }" |
|---|
| 285 |
@aliased_table_name = table_name |
|---|
| 286 |
@parent_table_name = parent.active_record.table_name |
|---|
| 287 |
|
|---|
| 288 |
if !join_dependency.table_joins.blank? && join_dependency.table_joins.to_s.downcase =~ %r{join(\s+\w+)?\s+ |
|---|
| 289 |
join_dependency.table_aliases[aliased_table_name] += 1 |
|---|
| 290 |
end |
|---|
| 291 |
|
|---|
| 292 |
unless join_dependency.table_aliases[aliased_table_name].zero? |
|---|
| 293 |
|
|---|
| 294 |
@aliased_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}" |
|---|
| 295 |
table_index = join_dependency.table_aliases[aliased_table_name] |
|---|
| 296 |
join_dependency.table_aliases[aliased_table_name] += 1 |
|---|
| 297 |
@aliased_table_name = @aliased_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0 |
|---|
| 298 |
else |
|---|
| 299 |
join_dependency.table_aliases[aliased_table_name] += 1 |
|---|
| 300 |
end |
|---|
| 301 |
|
|---|
| 302 |
if reflection.macro == :has_and_belongs_to_many || (reflection.macro == :has_many && reflection.options[:through]) |
|---|
| 303 |
@aliased_join_table_name = reflection.macro == :has_and_belongs_to_many ? reflection.options[:join_table] : reflection.through_reflection.klass.table_name |
|---|
| 304 |
unless join_dependency.table_aliases[aliased_join_table_name].zero? |
|---|
| 305 |
@aliased_join_table_name = active_record.connection.table_alias_for "#{pluralize(reflection.name)}_#{parent_table_name}_join" |
|---|
| 306 |
table_index = join_dependency.table_aliases[aliased_join_table_name] |
|---|
| 307 |
join_dependency.table_aliases[aliased_join_table_name] += 1 |
|---|
| 308 |
@aliased_join_table_name = @aliased_join_table_name[0..active_record.connection.table_alias_length-3] + "_#{table_index+1}" if table_index > 0 |
|---|
| 309 |
else |
|---|
| 310 |
join_dependency.table_aliases[aliased_join_table_name] += 1 |
|---|
| 311 |
end |
|---|
| 312 |
end |
|---|
| 313 |
end |
|---|
| 314 |
|
|---|
| 315 |
|
|---|
| 316 |
|
|---|
| 317 |
def aliased_primary_key |
|---|
| 318 |
"_#{ aliased_prefix }_r0" |
|---|
| 319 |
end |
|---|
| 320 |
|
|---|
| 321 |
|
|---|
| 322 |
|
|---|
| 323 |
def column_names_with_alias |
|---|
| 324 |
unless @column_names_with_alias |
|---|
| 325 |
@column_names_with_alias = [] |
|---|
| 326 |
([primary_key] + (@selected_attrs - [primary_key])).each_with_index do |column_name, i| |
|---|
| 327 |
@column_names_with_alias << [column_name, "_#{ aliased_prefix }_r#{ i }"] unless @selected_attrs.empty? |
|---|
| 328 |
end |
|---|
| 329 |
end |
|---|
| 330 |
return @column_names_with_alias |
|---|
| 331 |
end |
|---|
| 332 |
end |
|---|
| 333 |
end |
|---|
| 334 |
|
|---|
| 335 |
|
|---|
| 336 |
class ActiveRecord::Associations::ClassMethods::InnerJoinDependency |
|---|
| 337 |
protected |
|---|
| 338 |
def build_join_association(reflection, parent, attrs) |
|---|
| 339 |
InnerJoinAssociation.new(reflection, self, parent) |
|---|
| 340 |
end |
|---|
| 341 |
end |
|---|