Created
September 27, 2010 07:05
-
-
Save armstrjare/598710 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This file monkey patches ActiveRecord to fix the problem of: | |
# | |
# MoodelA <-> ModelB <-> ModelC | |
# ModelA.joins(:modelb) & ModelB.joins(:modelc) | |
# -> Would raise association not found because the association ("modelc") is searched for | |
# -> from the context of ModelA, rather than from the [expected] context of ModelB | |
# (expected because the joins associatin is specified where the association "modelc" exists within the base of the Relation (ModelB) ) | |
# -> The reason this ocurred is because the context of the joins on associations are not persisted when merging two relations/scopes | |
# -> rather, they are simply copied by name in to the merging relation/scope. | |
# | |
# -> This monkey patch adds functionality of persisting this context by wrapping the association join with a proxy that keeps the | |
# -> intended context of the association (keeps the ActiveRecord class at the base of the relation/scope where the join was defined). | |
# | |
# -> This allows for a lookup for the association reflection on the correct ActiveRecord class when the AREL is built. | |
module ActiveRecord::QueryMethods | |
def build_joins(relation, joins) | |
joined_associations = [] | |
association_joins = [] | |
joins = @joins_values.map {|j| j.respond_to?(:strip) ? j.strip : j}.uniq | |
joins.each do |join| | |
# armstrjare: added case for the join association proxy | |
association_joins << join if [Hash, Array, Symbol, ActiveRecord::SpawnMethods::AssociationJoinFromContext].include?(join.class) && !array_of_strings?(join) | |
# end added | |
end | |
stashed_association_joins = joins.grep(ActiveRecord::Associations::ClassMethods::JoinDependency::JoinAssociation) | |
non_association_joins = (joins - association_joins - stashed_association_joins) | |
custom_joins = custom_join_sql(*non_association_joins) | |
join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, association_joins, custom_joins) | |
join_dependency.graft(*stashed_association_joins) | |
@implicit_readonly = true unless association_joins.empty? && stashed_association_joins.empty? | |
to_join = [] | |
join_dependency.join_associations.each do |association| | |
if (association_relation = association.relation).is_a?(Array) | |
to_join << [association_relation.first, association.join_class, association.association_join.first] | |
to_join << [association_relation.last, association.join_class, association.association_join.last] | |
else | |
to_join << [association_relation, association.join_class, association.association_join] | |
end | |
end | |
to_join.each do |tj| | |
unless joined_associations.detect {|ja| ja[0] == tj[0] && ja[1] == tj[1] && ja[2] == tj[2] } | |
joined_associations << tj | |
relation = relation.join(tj[0], tj[1]).on(*tj[2]) | |
end | |
end | |
relation.join(custom_joins) | |
end | |
end | |
module ActiveRecord | |
module SpawnMethods | |
# Added | |
AssociationJoinFromContext = Struct.new(:context, :association) | |
def merge(r) | |
merged_relation = clone | |
return merged_relation unless r | |
((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - [:joins, :where]).each do |method| | |
value = r.send(:"#{method}_values") | |
unless value.empty? | |
if method == :includes | |
merged_relation = merged_relation.includes(value) | |
else | |
merged_relation.send(:"#{method}_values=", value) | |
end | |
end | |
end | |
# armstrjare: added to provide a proxy to the join association | |
new_joins = r.joins_values.map do |j| | |
case j | |
when Symbol, String | |
# Association join - keep the context of the association | |
AssociationJoinFromContext.new(r.klass, j) | |
else | |
j | |
end | |
end | |
merged_relation = merged_relation.joins(new_joins) | |
# end added | |
merged_wheres = @where_values | |
r.where_values.each do |w| | |
if w.respond_to?(:operator) && w.operator == :== | |
merged_wheres = merged_wheres.reject { |p| | |
p.respond_to?(:operator) && p.operator == :== && p.operand1.name == w.operand1.name | |
} | |
end | |
merged_wheres += [w] | |
end | |
merged_relation.where_values = merged_wheres | |
Relation::SINGLE_VALUE_METHODS.reject {|m| m == :lock}.each do |method| | |
unless (value = r.send(:"#{method}_value")).nil? | |
merged_relation.send(:"#{method}_value=", value) | |
end | |
end | |
merged_relation.lock_value = r.lock_value unless merged_relation.lock_value | |
# Apply scope extension modules | |
merged_relation.send :apply_modules, r.extensions | |
merged_relation | |
end | |
end | |
end | |
module ActiveRecord | |
module Associations | |
module ClassMethods | |
class JoinDependency | |
private | |
def build(associations, parent = nil, join_class = Arel::InnerJoin) | |
parent ||= @joins.last | |
case associations | |
# armstrjare: added case for the join association proxy | |
when ActiveRecord::SpawnMethods::AssociationJoinFromContext | |
reflection = associations.context.reflections[associations.association.to_s.to_sym] or | |
raise ConfigurationError, "[AJ] Association named '#{ associations.association }' was not found in #{associations.context.name}; perhaps you misspelled it?" | |
@reflections << reflection | |
@joins << build_join_association(reflection, JoinBase.new(associations.context, [associations.association])).with_join_class(join_class) | |
# end added | |
when Symbol, String | |
reflection = parent.reflections[associations.to_s.intern] or | |
raise ConfigurationError, "Association named '#{ associations }' was not found; perhaps you misspelled it?" | |
@reflections << reflection | |
@joins << build_join_association(reflection, parent).with_join_class(join_class) | |
when Array | |
associations.each do |association| | |
build(association, parent, join_class) | |
end | |
when Hash | |
associations.keys.sort{|a,b|a.to_s<=>b.to_s}.each do |name| | |
build(name, parent, join_class) | |
build(associations[name], nil, join_class) | |
end | |
else | |
raise ConfigurationError, associations.inspect | |
end | |
end | |
end | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment