aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--activerecord/lib/active_record/relation.rb2
-rw-r--r--activerecord/lib/active_record/relation/merger.rb113
-rw-r--r--activerecord/lib/active_record/relation/spawn_methods.rb7
-rw-r--r--activerecord/test/cases/relation_test.rb22
4 files changed, 95 insertions, 49 deletions
diff --git a/activerecord/lib/active_record/relation.rb b/activerecord/lib/active_record/relation.rb
index 0d7da67c98..88fb0e9dda 100644
--- a/activerecord/lib/active_record/relation.rb
+++ b/activerecord/lib/active_record/relation.rb
@@ -11,7 +11,7 @@ module ActiveRecord
:order, :joins, :where, :having, :bind, :references]
SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
- :reverse_order, :uniq]
+ :reverse_order, :uniq, :create_with]
include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
diff --git a/activerecord/lib/active_record/relation/merger.rb b/activerecord/lib/active_record/relation/merger.rb
index 86e7f3ae0f..23629eedf6 100644
--- a/activerecord/lib/active_record/relation/merger.rb
+++ b/activerecord/lib/active_record/relation/merger.rb
@@ -14,6 +14,40 @@ module ActiveRecord
end
def merge
+ HashMerger.new(relation, other_hash).merge
+ end
+
+ private
+
+ def other_hash
+ hash = {}
+ Relation::MULTI_VALUE_METHODS.map { |name| hash[name] = other.send("#{name}_values") }
+ Relation::SINGLE_VALUE_METHODS.map { |name| hash[name] = other.send("#{name}_value") }
+ hash[:extensions] = other.extensions
+ hash
+ end
+ end
+
+ class HashMerger
+ attr_reader :relation, :values
+
+ def initialize(relation, values)
+ @relation = relation
+ @values = values
+ end
+
+ def normal_values
+ Relation::SINGLE_VALUE_METHODS +
+ Relation::MULTI_VALUE_METHODS -
+ [:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering]
+ end
+
+ def merge
+ normal_values.each do |name|
+ value = values[name]
+ relation.send("#{name}!", value) unless value.blank?
+ end
+
merge_multi_values
merge_single_values
@@ -23,70 +57,61 @@ module ActiveRecord
private
def merge_multi_values
- values = Relation::MULTI_VALUE_METHODS - [:where, :order, :bind]
-
- values.each do |method|
- value = other.send(:"#{method}_values")
-
- unless value.empty?
- relation.send("#{method}!", value)
- end
- end
-
relation.where_values = merged_wheres
relation.bind_values = merged_binds
- if other.reordering_value
+ if values[:reordering]
# override any order specified in the original relation
- relation.reorder! other.order_values
- else
+ relation.reorder! values[:order]
+ elsif values[:order]
# merge in order_values from r
- relation.order_values += other.order_values
+ relation.order_values += values[:order]
end
# Apply scope extension modules
- relation.send :apply_modules, other.extensions
+ relation.send :apply_modules, values[:extensions] if values[:extensions]
end
def merge_single_values
- values = Relation::SINGLE_VALUE_METHODS - [:reverse_order, :lock, :create_with, :reordering]
+ relation.lock_value = values[:lock] unless relation.lock_value
+ relation.reverse_order_value = values[:reverse_order]
- values.each do |method|
- value = other.send(:"#{method}_value")
- relation.send("#{method}!", value) if value
- end
-
- relation.lock_value = other.lock_value unless relation.lock_value
- relation.reverse_order_value = other.reverse_order_value
-
- unless other.create_with_value.empty?
- relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
+ unless values[:create_with].blank?
+ relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with])
end
end
def merged_binds
- (relation.bind_values + other.bind_values).uniq(&:first)
+ if values[:bind]
+ (relation.bind_values + values[:bind]).uniq(&:first)
+ else
+ relation.bind_values
+ end
end
def merged_wheres
- merged_wheres = relation.where_values + other.where_values
-
- unless relation.where_values.empty?
- # Remove duplicates, 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 == :==
- name = w.left.name
- table = w.left.relation.name
- nuke = seen[table][name]
- seen[table][name] = true
- end
- nuke
- }.reverse
- end
+ if values[:where]
+ merged_wheres = relation.where_values + values[:where]
+
+ unless relation.where_values.empty?
+ # Remove duplicates, 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 == :==
+ name = w.left.name
+ table = w.left.relation.name
+ nuke = seen[table][name]
+ seen[table][name] = true
+ end
+ nuke
+ }.reverse
+ end
- merged_wheres
+ merged_wheres
+ else
+ relation.where_values
+ end
end
end
end
diff --git a/activerecord/lib/active_record/relation/spawn_methods.rb b/activerecord/lib/active_record/relation/spawn_methods.rb
index 80c129d361..40af665769 100644
--- a/activerecord/lib/active_record/relation/spawn_methods.rb
+++ b/activerecord/lib/active_record/relation/spawn_methods.rb
@@ -5,10 +5,13 @@ module ActiveRecord
module SpawnMethods
def merge(other)
if other
- if other.is_a?(Array)
+ case other
+ when Array
to_a & other
+ when Hash
+ Relation::HashMerger.new(clone, other).merge
else
- ActiveRecord::Relation::Merger.new(clone, other).merge
+ Relation::Merger.new(clone, other).merge
end
else
self
diff --git a/activerecord/test/cases/relation_test.rb b/activerecord/test/cases/relation_test.rb
index 5eca3e05c4..a38255eda6 100644
--- a/activerecord/test/cases/relation_test.rb
+++ b/activerecord/test/cases/relation_test.rb
@@ -21,9 +21,10 @@ module ActiveRecord
def test_initialize_single_values
relation = Relation.new :a, :b
- Relation::SINGLE_VALUE_METHODS.each do |method|
+ (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method|
assert_nil relation.send("#{method}_value"), method.to_s
end
+ assert_equal({}, relation.create_with_value)
end
def test_multi_value_initialize
@@ -132,6 +133,18 @@ module ActiveRecord
relation = relation.apply_finder_options(:references => :foo)
assert_equal ['foo'], relation.references_values
end
+
+ test 'merging a hash into a relation' do
+ relation = Relation.new :a, :b
+ relation = relation.merge where: ['lol'], readonly: true
+
+ assert_equal ['lol'], relation.where_values
+ assert_equal true, relation.readonly_value
+ end
+
+ test 'merging an empty hash into a relation' do
+ assert_equal [], Relation.new(:a, :b).merge({}).where_values
+ end
end
class RelationMutationTest < ActiveSupport::TestCase
@@ -151,7 +164,7 @@ module ActiveRecord
assert relation.references_values.include?('foo')
end
- (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order]).each do |method|
+ (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with]).each do |method|
test "##{method}!" do
assert relation.public_send("#{method}!", :foo).equal?(relation)
assert_equal :foo, relation.public_send("#{method}_value")
@@ -184,5 +197,10 @@ module ActiveRecord
assert relation.extending!(mod).equal?(relation)
assert relation.is_a?(mod)
end
+
+ test 'create_with!' do
+ assert relation.create_with!(foo: 'bar').equal?(relation)
+ assert_equal({foo: 'bar'}, relation.create_with_value)
+ end
end
end