diff options
-rw-r--r-- | activerecord/lib/active_record/relation.rb | 2 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation/merger.rb | 113 | ||||
-rw-r--r-- | activerecord/lib/active_record/relation/spawn_methods.rb | 7 | ||||
-rw-r--r-- | activerecord/test/cases/relation_test.rb | 22 |
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 |