Skip to content
This repository
Browse code

Allow Relation#merge to take a hash

  • Loading branch information...
commit 0183193a6aecefb3b55a5abf20792aee6b57dcc1 1 parent e4f0fbd
Jon Leighton authored April 13, 2012
2  activerecord/lib/active_record/relation.rb
@@ -11,7 +11,7 @@ class Relation
11 11
                             :order, :joins, :where, :having, :bind, :references]
12 12
 
13 13
     SINGLE_VALUE_METHODS = [:limit, :offset, :lock, :readonly, :from, :reordering,
14  
-                            :reverse_order, :uniq]
  14
+                            :reverse_order, :uniq, :create_with]
15 15
 
16 16
     include FinderMethods, Calculations, SpawnMethods, QueryMethods, Batches, Explain, Delegation
17 17
 
113  activerecord/lib/active_record/relation/merger.rb
@@ -14,6 +14,40 @@ def initialize(relation, other)
14 14
       end
15 15
 
16 16
       def merge
  17
+        HashMerger.new(relation, other_hash).merge
  18
+      end
  19
+
  20
+      private
  21
+
  22
+      def other_hash
  23
+        hash = {}
  24
+        Relation::MULTI_VALUE_METHODS.map  { |name| hash[name] = other.send("#{name}_values") }
  25
+        Relation::SINGLE_VALUE_METHODS.map { |name| hash[name] = other.send("#{name}_value")  }
  26
+        hash[:extensions] = other.extensions
  27
+        hash
  28
+      end
  29
+    end
  30
+
  31
+    class HashMerger
  32
+      attr_reader :relation, :values
  33
+
  34
+      def initialize(relation, values)
  35
+        @relation = relation
  36
+        @values   = values
  37
+      end
  38
+
  39
+      def normal_values
  40
+        Relation::SINGLE_VALUE_METHODS +
  41
+          Relation::MULTI_VALUE_METHODS -
  42
+          [:where, :order, :bind, :reverse_order, :lock, :create_with, :reordering]
  43
+      end
  44
+
  45
+      def merge
  46
+        normal_values.each do |name|
  47
+          value = values[name]
  48
+          relation.send("#{name}!", value) unless value.blank?
  49
+        end
  50
+
17 51
         merge_multi_values
18 52
         merge_single_values
19 53
 
@@ -23,70 +57,61 @@ def merge
23 57
       private
24 58
 
25 59
       def merge_multi_values
26  
-        values = Relation::MULTI_VALUE_METHODS - [:where, :order, :bind]
27  
-
28  
-        values.each do |method|
29  
-          value = other.send(:"#{method}_values")
30  
-
31  
-          unless value.empty?
32  
-            relation.send("#{method}!", value)
33  
-          end
34  
-        end
35  
-
36 60
         relation.where_values = merged_wheres
37 61
         relation.bind_values  = merged_binds
38 62
 
39  
-        if other.reordering_value
  63
+        if values[:reordering]
40 64
           # override any order specified in the original relation
41  
-          relation.reorder! other.order_values
42  
-        else
  65
+          relation.reorder! values[:order]
  66
+        elsif values[:order]
43 67
           # merge in order_values from r
44  
-          relation.order_values += other.order_values
  68
+          relation.order_values += values[:order]
45 69
         end
46 70
 
47 71
         # Apply scope extension modules
48  
-        relation.send :apply_modules, other.extensions
  72
+        relation.send :apply_modules, values[:extensions] if values[:extensions]
49 73
       end
50 74
 
51 75
       def merge_single_values
52  
-        values = Relation::SINGLE_VALUE_METHODS - [:reverse_order, :lock, :create_with, :reordering]
  76
+        relation.lock_value          = values[:lock] unless relation.lock_value
  77
+        relation.reverse_order_value = values[:reverse_order]
53 78
 
54  
-        values.each do |method|
55  
-          value = other.send(:"#{method}_value")
56  
-          relation.send("#{method}!", value) if value
57  
-        end
58  
-
59  
-        relation.lock_value          = other.lock_value unless relation.lock_value
60  
-        relation.reverse_order_value = other.reverse_order_value
61  
-
62  
-        unless other.create_with_value.empty?
63  
-          relation.create_with_value = (relation.create_with_value || {}).merge(other.create_with_value)
  79
+        unless values[:create_with].blank?
  80
+          relation.create_with_value = (relation.create_with_value || {}).merge(values[:create_with])
64 81
         end
65 82
       end
66 83
 
67 84
       def merged_binds
68  
-        (relation.bind_values + other.bind_values).uniq(&:first)
  85
+        if values[:bind]
  86
+          (relation.bind_values + values[:bind]).uniq(&:first)
  87
+        else
  88
+          relation.bind_values
  89
+        end
69 90
       end
70 91
 
71 92
       def merged_wheres
72  
-        merged_wheres = relation.where_values + other.where_values
73  
-
74  
-        unless relation.where_values.empty?
75  
-          # Remove duplicates, last one wins.
76  
-          seen = Hash.new { |h,table| h[table] = {} }
77  
-          merged_wheres = merged_wheres.reverse.reject { |w|
78  
-            nuke = false
79  
-            if w.respond_to?(:operator) && w.operator == :==
80  
-              name              = w.left.name
81  
-              table             = w.left.relation.name
82  
-              nuke              = seen[table][name]
83  
-              seen[table][name] = true
84  
-            end
85  
-            nuke
86  
-          }.reverse
87  
-        end
  93
+        if values[:where]
  94
+          merged_wheres = relation.where_values + values[:where]
  95
+
  96
+          unless relation.where_values.empty?
  97
+            # Remove duplicates, last one wins.
  98
+            seen = Hash.new { |h,table| h[table] = {} }
  99
+            merged_wheres = merged_wheres.reverse.reject { |w|
  100
+              nuke = false
  101
+              if w.respond_to?(:operator) && w.operator == :==
  102
+                name              = w.left.name
  103
+                table             = w.left.relation.name
  104
+                nuke              = seen[table][name]
  105
+                seen[table][name] = true
  106
+              end
  107
+              nuke
  108
+            }.reverse
  109
+          end
88 110
 
89  
-        merged_wheres
  111
+          merged_wheres
  112
+        else
  113
+          relation.where_values
  114
+        end
90 115
       end
91 116
     end
92 117
   end
7  activerecord/lib/active_record/relation/spawn_methods.rb
@@ -5,10 +5,13 @@ module ActiveRecord
5 5
   module SpawnMethods
6 6
     def merge(other)
7 7
       if other
8  
-        if other.is_a?(Array)
  8
+        case other
  9
+        when Array
9 10
           to_a & other
  11
+        when Hash
  12
+          Relation::HashMerger.new(clone, other).merge
10 13
         else
11  
-          ActiveRecord::Relation::Merger.new(clone, other).merge
  14
+          Relation::Merger.new(clone, other).merge
12 15
         end
13 16
       else
14 17
         self
22  activerecord/test/cases/relation_test.rb
@@ -21,9 +21,10 @@ def test_construction
21 21
 
22 22
     def test_initialize_single_values
23 23
       relation = Relation.new :a, :b
24  
-      Relation::SINGLE_VALUE_METHODS.each do |method|
  24
+      (Relation::SINGLE_VALUE_METHODS - [:create_with]).each do |method|
25 25
         assert_nil relation.send("#{method}_value"), method.to_s
26 26
       end
  27
+      assert_equal({}, relation.create_with_value)
27 28
     end
28 29
 
29 30
     def test_multi_value_initialize
@@ -132,6 +133,18 @@ def test_apply_finder_options_takes_references
132 133
       relation = relation.apply_finder_options(:references => :foo)
133 134
       assert_equal ['foo'], relation.references_values
134 135
     end
  136
+
  137
+    test 'merging a hash into a relation' do
  138
+      relation = Relation.new :a, :b
  139
+      relation = relation.merge where: ['lol'], readonly: true
  140
+
  141
+      assert_equal ['lol'], relation.where_values
  142
+      assert_equal true, relation.readonly_value
  143
+    end
  144
+
  145
+    test 'merging an empty hash into a relation' do
  146
+      assert_equal [], Relation.new(:a, :b).merge({}).where_values
  147
+    end
135 148
   end
136 149
 
137 150
   class RelationMutationTest < ActiveSupport::TestCase
@@ -151,7 +164,7 @@ def relation
151 164
       assert relation.references_values.include?('foo')
152 165
     end
153 166
 
154  
-    (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order]).each do |method|
  167
+    (Relation::SINGLE_VALUE_METHODS - [:lock, :reordering, :reverse_order, :create_with]).each do |method|
155 168
       test "##{method}!" do
156 169
         assert relation.public_send("#{method}!", :foo).equal?(relation)
157 170
         assert_equal :foo, relation.public_send("#{method}_value")
@@ -184,5 +197,10 @@ def relation
184 197
       assert relation.extending!(mod).equal?(relation)
185 198
       assert relation.is_a?(mod)
186 199
     end
  200
+
  201
+    test 'create_with!' do
  202
+      assert relation.create_with!(foo: 'bar').equal?(relation)
  203
+      assert_equal({foo: 'bar'}, relation.create_with_value)
  204
+    end
187 205
   end
188 206
 end

0 notes on commit 0183193

Please sign in to comment.
Something went wrong with that request. Please try again.