Created
January 10, 2014 13:18
-
-
Save Bertg/8351770 to your computer and use it in GitHub Desktop.
This version of spawn methods removes the dangerous behaviour where rails 3.2 (after 3.2.13) clobbers conditions.
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
raise "Remove patch after rails 3.2" if Rails.version =~ /\A4/ | |
# copy of lib/active_record/relation/spawn_methods.rb | |
# Because updating to this version 3.2.16 might change the behaviour of some of our queries, | |
# we took a copy of this class and remove the behaviour where | |
module ActiveRecord | |
module SpawnMethods | |
def merge(r) | |
return self unless r | |
return to_a & r if r.is_a?(Array) | |
merged_relation = clone | |
r = r.with_default_scope if r.default_scoped? && r.klass != klass | |
Relation::ASSOCIATION_METHODS.each do |method| | |
value = r.send(:"#{method}_values") | |
unless value.empty? | |
if method == :includes | |
merged_relation = merged_relation.includes(value) | |
else | |
merge_relation_method(merged_relation, method, value) | |
end | |
end | |
end | |
(Relation::MULTI_VALUE_METHODS - [:joins, :where, :order]).each do |method| | |
value = r.send(:"#{method}_values") | |
merge_relation_method(merged_relation, method, value) if value.present? | |
end | |
merge_joins(merged_relation, r) | |
merged_wheres = @where_values + r.where_values | |
# -- PATCH | |
# unless @where_values.empty? | |
# # Remove duplicate ARel attributes. Last one wins. | |
# seen = Hash.new { |h,table| h[table] = Hash.new { |i,name| i[name] = [] } } | |
# merged_wheres = merged_wheres.reverse.reject { |w| | |
# nuke = false | |
# if w.respond_to?(:operator) && w.operator == :== && | |
# w.left.respond_to?(:relation) | |
# name = w.left.name | |
# table = w.left.relation.name | |
# nuke = !seen[table][name].empty? | |
# seen[table][name] << w.right | |
# end | |
# nuke | |
# }.reverse | |
# end | |
# -- END PATCH | |
merged_relation.where_values = merged_wheres | |
(Relation::SINGLE_VALUE_METHODS - [:lock, :create_with, :reordering]).each do |method| | |
value = r.send(:"#{method}_value") | |
merged_relation.send(:"#{method}_value=", value) unless value.nil? | |
end | |
merged_relation.lock_value = r.lock_value unless merged_relation.lock_value | |
merged_relation = merged_relation.create_with(r.create_with_value) unless r.create_with_value.empty? | |
if (r.reordering_value) | |
# override any order specified in the original relation | |
merged_relation.reordering_value = true | |
merged_relation.order_values = r.order_values | |
else | |
# merge in order_values from r | |
merged_relation.order_values += r.order_values | |
end | |
# Apply scope extension modules | |
merged_relation.send :apply_modules, r.extensions | |
merged_relation | |
end | |
# Removes from the query the condition(s) specified in +skips+. | |
# | |
# Example: | |
# | |
# Post.order('id asc').except(:order) # discards the order condition | |
# Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order | |
# | |
def except(*skips) | |
result = self.class.new(@klass, table) | |
result.default_scoped = default_scoped | |
((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - skips).each do |method| | |
result.send(:"#{method}_values=", send(:"#{method}_values")) | |
end | |
(Relation::SINGLE_VALUE_METHODS - skips).each do |method| | |
result.send(:"#{method}_value=", send(:"#{method}_value")) | |
end | |
# Apply scope extension modules | |
result.send(:apply_modules, extensions) | |
result | |
end | |
# Removes any condition from the query other than the one(s) specified in +onlies+. | |
# | |
# Example: | |
# | |
# Post.order('id asc').only(:where) # discards the order condition | |
# Post.order('id asc').only(:where, :order) # uses the specified order | |
# | |
def only(*onlies) | |
result = self.class.new(@klass, table) | |
result.default_scoped = default_scoped | |
((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) & onlies).each do |method| | |
result.send(:"#{method}_values=", send(:"#{method}_values")) | |
end | |
(Relation::SINGLE_VALUE_METHODS & onlies).each do |method| | |
result.send(:"#{method}_value=", send(:"#{method}_value")) | |
end | |
# Apply scope extension modules | |
result.send(:apply_modules, extensions) | |
result | |
end | |
VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, :extend, | |
:order, :select, :readonly, :group, :having, :from, :lock ] | |
def apply_finder_options(options) | |
relation = clone | |
return relation unless options | |
options.assert_valid_keys(VALID_FIND_OPTIONS) | |
finders = options.dup | |
finders.delete_if { |key, value| value.nil? && key != :limit } | |
([:joins, :select, :group, :order, :having, :limit, :offset, :from, :lock, :readonly] & finders.keys).each do |finder| | |
relation = relation.send(finder, finders[finder]) | |
end | |
relation = relation.where(finders[:conditions]) if options.has_key?(:conditions) | |
relation = relation.includes(finders[:include]) if options.has_key?(:include) | |
relation = relation.extending(finders[:extend]) if options.has_key?(:extend) | |
relation | |
end | |
private | |
def merge_joins(relation, other) | |
values = other.joins_values | |
return if values.blank? | |
if other.klass >= relation.klass | |
relation.joins_values += values | |
else | |
joins_dependency, rest = values.partition do |join| | |
case join | |
when Hash, Symbol, Array | |
true | |
else | |
false | |
end | |
end | |
join_dependency = ActiveRecord::Associations::JoinDependency.new( | |
other.klass, | |
joins_dependency, | |
[] | |
) | |
relation.joins_values += join_dependency.join_associations + rest | |
end | |
end | |
def merge_relation_method(relation, method, value) | |
relation.send(:"#{method}_values=", relation.send(:"#{method}_values") + value) | |
end | |
end | |
end |
Here's the same patch, updated for rails 3.2.17
and remove already defined constants warnings
raise "Remove patch after rails 3.2" if Rails.version =~ /\A4/
# copy of lib/active_record/relation/spawn_methods.rb
# Because updating to this version 3.2.16 might change the behaviour of some of our queries,
# we took a copy of this class and remove the behaviour where
module ActiveRecord
module SpawnMethods
def merge(r)
return self unless r
return to_a & r if r.is_a?(Array)
merged_relation = clone
r = r.with_default_scope if r.default_scoped? && r.klass != klass
Relation::ASSOCIATION_METHODS.each do |method|
value = r.send(:"#{method}_values")
unless value.empty?
if method == :includes
merged_relation = merged_relation.includes(value)
else
merge_relation_method(merged_relation, method, value)
end
end
end
(Relation::MULTI_VALUE_METHODS - [:joins, :where, :order]).each do |method|
value = r.send(:"#{method}_values")
merge_relation_method(merged_relation, method, value) if value.present?
end
merge_joins(merged_relation, r)
merged_wheres = @where_values + r.where_values
# -- PATCH
# unless @where_values.empty?
# # Remove duplicate ARel attributes. Last one wins.
# seen = Hash.new { |h,table| h[table] = {} }
# merged_wheres = merged_wheres.reverse.reject { |w|
# nuke = false
# if w.respond_to?(:operator) && w.operator == :== &&
# w.left.respond_to?(:relation)
# name = w.left.name
# table = w.left.relation.name
# nuke = seen[table][name]
# seen[table][name] = true
# end
# nuke
# }.reverse
# end
# -- END PATCH
merged_relation.where_values = merged_wheres
(Relation::SINGLE_VALUE_METHODS - [:lock, :create_with, :reordering]).each do |method|
value = r.send(:"#{method}_value")
merged_relation.send(:"#{method}_value=", value) unless value.nil?
end
merged_relation.lock_value = r.lock_value unless merged_relation.lock_value
merged_relation = merged_relation.create_with(r.create_with_value) unless r.create_with_value.empty?
if (r.reordering_value)
# override any order specified in the original relation
merged_relation.reordering_value = true
merged_relation.order_values = r.order_values
else
# merge in order_values from r
merged_relation.order_values += r.order_values
end
# Apply scope extension modules
merged_relation.send :apply_modules, r.extensions
merged_relation
end
# Removes from the query the condition(s) specified in +skips+.
#
# Example:
#
# Post.order('id asc').except(:order) # discards the order condition
# Post.where('id > 10').order('id asc').except(:where) # discards the where condition but keeps the order
#
def except(*skips)
result = self.class.new(@klass, table)
result.default_scoped = default_scoped
((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) - skips).each do |method|
result.send(:"#{method}_values=", send(:"#{method}_values"))
end
(Relation::SINGLE_VALUE_METHODS - skips).each do |method|
result.send(:"#{method}_value=", send(:"#{method}_value"))
end
# Apply scope extension modules
result.send(:apply_modules, extensions)
result
end
# Removes any condition from the query other than the one(s) specified in +onlies+.
#
# Example:
#
# Post.order('id asc').only(:where) # discards the order condition
# Post.order('id asc').only(:where, :order) # uses the specified order
#
def only(*onlies)
result = self.class.new(@klass, table)
result.default_scoped = default_scoped
((Relation::ASSOCIATION_METHODS + Relation::MULTI_VALUE_METHODS) & onlies).each do |method|
result.send(:"#{method}_values=", send(:"#{method}_values"))
end
(Relation::SINGLE_VALUE_METHODS & onlies).each do |method|
result.send(:"#{method}_value=", send(:"#{method}_value"))
end
# Apply scope extension modules
result.send(:apply_modules, extensions)
result
end
# -- PATCH
# VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset, :extend,
# :order, :select, :readonly, :group, :having, :from, :lock ]
# -- END PATCH
def apply_finder_options(options)
relation = clone
return relation unless options
options.assert_valid_keys(VALID_FIND_OPTIONS)
finders = options.dup
finders.delete_if { |key, value| value.nil? && key != :limit }
([:joins, :select, :group, :order, :having, :limit, :offset, :from, :lock, :readonly] & finders.keys).each do |finder|
relation = relation.send(finder, finders[finder])
end
relation = relation.where(finders[:conditions]) if options.has_key?(:conditions)
relation = relation.includes(finders[:include]) if options.has_key?(:include)
relation = relation.extending(finders[:extend]) if options.has_key?(:extend)
relation
end
private
def merge_joins(relation, other)
values = other.joins_values
return if values.blank?
if other.klass >= relation.klass
relation.joins_values += values
else
joins_dependency, rest = values.partition do |join|
case join
when Hash, Symbol, Array
true
else
false
end
end
join_dependency = ActiveRecord::Associations::JoinDependency.new(
other.klass,
joins_dependency,
[]
)
relation.joins_values += join_dependency.join_associations + rest
end
end
def merge_relation_method(relation, method, value)
relation.send(:"#{method}_values=", relation.send(:"#{method}_values") + value)
end
end
end
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
bert, have you shipped this patch and found it to work as expected? Want to pick it up, but wondering how stable and tested it is :-) Thanks for putting it together...