public
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
Merge scoped :joins together instead of overwriting them. May expose scoping 
bugs in your code!

[#501 state:resolved]

Signed-off-by: Jeremy Kemper <jeremy@bitsweat.net>
pixeltrix (author)
Thu Aug 28 09:00:18 -0700 2008
jeremy (committer)
Thu Aug 28 12:07:15 -0700 2008
commit  db22c89543f45d7f27847003af949afa21cb6fa1
tree    ed7d27eecfca62a9147577a26402e02d5600b1ee
parent  44af2efa2c7391681968c827ca47201a0a02e974
...
1599
1600
1601
1602
 
1603
1604
1605
...
1655
1656
1657
1658
 
1659
1660
1661
...
1599
1600
1601
 
1602
1603
1604
1605
...
1655
1656
1657
 
1658
1659
1660
1661
0
@@ -1599,7 +1599,7 @@ module ActiveRecord
0
           sql = "SELECT #{column_aliases(join_dependency)} FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
0
           sql << join_dependency.join_associations.collect{|join| join.association_join }.join
0
 
0
-          add_joins!(sql, options, scope)
0
+          add_joins!(sql, options[:joins], scope)
0
           add_conditions!(sql, options[:conditions], scope)
0
           add_limited_ids_condition!(sql, options, join_dependency) if !using_limitable_reflections?(join_dependency.reflections) && ((scope && scope[:limit]) || options[:limit])
0
 
0
@@ -1655,7 +1655,7 @@ module ActiveRecord
0
 
0
           if is_distinct
0
             sql << distinct_join_associations.collect { |assoc| assoc.association_join }.join
0
-            add_joins!(sql, options, scope)
0
+            add_joins!(sql, options[:joins], scope)
0
           end
0
 
0
           add_conditions!(sql, options[:conditions], scope)
...
1549
1550
1551
1552
 
1553
1554
1555
...
1565
1566
1567
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1568
1569
1570
...
1620
1621
1622
1623
 
1624
1625
1626
1627
1628
1629
1630
1631
1632
 
 
 
 
 
 
 
1633
1634
1635
...
1879
1880
1881
 
 
1882
1883
1884
...
1549
1550
1551
 
1552
1553
1554
1555
...
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
...
1636
1637
1638
 
1639
1640
 
 
 
 
 
 
 
 
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
...
1894
1895
1896
1897
1898
1899
1900
1901
0
@@ -1549,7 +1549,7 @@ module ActiveRecord #:nodoc:
0
           sql  = "SELECT #{options[:select] || (scope && scope[:select]) || ((options[:joins] || (scope && scope[:joins])) && quoted_table_name + '.*') || '*'} "
0
           sql << "FROM #{(scope && scope[:from]) || options[:from] || quoted_table_name} "
0
 
0
-          add_joins!(sql, options, scope)
0
+          add_joins!(sql, options[:joins], scope)
0
           add_conditions!(sql, options[:conditions], scope)
0
 
0
           add_group!(sql, options[:group], scope)
0
@@ -1565,6 +1565,22 @@ module ActiveRecord #:nodoc:
0
          (safe_to_array(first) + safe_to_array(second)).uniq
0
         end
0
 
0
+        def merge_joins(first, second)
0
+          if first.is_a?(String) && second.is_a?(String)
0
+            "#{first} #{second}"
0
+          elsif first.is_a?(String) || second.is_a?(String)
0
+            if first.is_a?(String)
0
+              join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, second, nil)
0
+              "#{first} #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join}"
0
+            else
0
+              join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, first, nil)
0
+              "#{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} #{second}"
0
+            end
0
+          else
0
+            (safe_to_array(first) + safe_to_array(second)).uniq
0
+          end
0
+        end
0
+
0
         # Object#to_a is deprecated, though it does have the desired behavior
0
         def safe_to_array(o)
0
           case o
0
@@ -1620,16 +1636,15 @@ module ActiveRecord #:nodoc:
0
         end
0
 
0
         # The optional scope argument is for the current <tt>:find</tt> scope.
0
-        def add_joins!(sql, options, scope = :auto)
0
+        def add_joins!(sql, joins, scope = :auto)
0
           scope = scope(:find) if :auto == scope
0
-          [(scope && scope[:joins]), options[:joins]].each do |join|
0
-            case join
0
-            when Symbol, Hash, Array
0
-              join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, join, nil)
0
-              sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
0
-            else
0
-              sql << " #{join} "
0
-            end
0
+          merged_joins = scope && scope[:joins] && joins ? merge_joins(scope[:joins], joins) : (joins || scope && scope[:joins])
0
+          case merged_joins
0
+          when Symbol, Hash, Array
0
+            join_dependency = ActiveRecord::Associations::ClassMethods::InnerJoinDependency.new(self, merged_joins, nil)
0
+            sql << " #{join_dependency.join_associations.collect { |assoc| assoc.association_join }.join} "
0
+          when String
0
+            sql << " #{merged_joins} "
0
           end
0
         end
0
 
0
@@ -1879,6 +1894,8 @@ module ActiveRecord #:nodoc:
0
                         hash[method][key] = merge_conditions(params[key], hash[method][key])
0
                       elsif key == :include && merge
0
                         hash[method][key] = merge_includes(hash[method][key], params[key]).uniq
0
+                      elsif key == :joins && merge
0
+                        hash[method][key] = merge_joins(params[key], hash[method][key])
0
                       else
0
                         hash[method][key] = hash[method][key] || params[key]
0
                       end
...
188
189
190
191
 
192
193
194
...
188
189
190
 
191
192
193
194
0
@@ -188,7 +188,7 @@ module ActiveRecord
0
           end
0
 
0
           joins = ""
0
-          add_joins!(joins, options, scope)
0
+          add_joins!(joins, options[:joins], scope)
0
 
0
           if merged_includes.any?
0
             join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merged_includes, joins)
...
1
 
2
3
4
...
6
7
8
9
 
10
11
12
...
97
98
99
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
101
102
...
152
153
154
155
 
156
157
158
...
357
358
359
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
361
362
...
1
2
3
4
5
...
7
8
9
 
10
11
12
13
...
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
...
193
194
195
 
196
197
198
199
...
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
0
@@ -1,4 +1,5 @@
0
 require "cases/helper"
0
+require 'models/author'
0
 require 'models/developer'
0
 require 'models/project'
0
 require 'models/comment'
0
@@ -6,7 +7,7 @@ require 'models/post'
0
 require 'models/category'
0
 
0
 class MethodScopingTest < ActiveRecord::TestCase
0
-  fixtures :developers, :projects, :comments, :posts, :developers_projects
0
+  fixtures :authors, :developers, :projects, :comments, :posts, :developers_projects
0
 
0
   def test_set_conditions
0
     Developer.with_scope(:find => { :conditions => 'just a test...' }) do
0
@@ -97,6 +98,46 @@ class MethodScopingTest < ActiveRecord::TestCase
0
     assert_equal developers(:david).attributes, scoped_developers.first.attributes
0
   end
0
 
0
+  def test_scoped_find_using_new_style_joins
0
+    scoped_developers = Developer.with_scope(:find => { :joins => :projects }) do
0
+      Developer.find(:all, :conditions => 'projects.id = 2')
0
+    end
0
+    assert scoped_developers.include?(developers(:david))
0
+    assert !scoped_developers.include?(developers(:jamis))
0
+    assert_equal 1, scoped_developers.size
0
+    assert_equal developers(:david).attributes, scoped_developers.first.attributes
0
+  end
0
+
0
+  def test_scoped_find_merges_old_style_joins
0
+    scoped_authors = Author.with_scope(:find => { :joins => 'INNER JOIN posts ON authors.id = posts.author_id ' }) do
0
+      Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'INNER JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1')
0
+    end
0
+    assert scoped_authors.include?(authors(:david))
0
+    assert !scoped_authors.include?(authors(:mary))
0
+    assert_equal 1, scoped_authors.size
0
+    assert_equal authors(:david).attributes, scoped_authors.first.attributes
0
+  end
0
+
0
+  def test_scoped_find_merges_new_style_joins
0
+    scoped_authors = Author.with_scope(:find => { :joins => :posts }) do
0
+      Author.find(:all, :select => 'DISTINCT authors.*', :joins => :comments, :conditions => 'comments.id = 1')
0
+    end
0
+    assert scoped_authors.include?(authors(:david))
0
+    assert !scoped_authors.include?(authors(:mary))
0
+    assert_equal 1, scoped_authors.size
0
+    assert_equal authors(:david).attributes, scoped_authors.first.attributes
0
+  end
0
+
0
+  def test_scoped_find_merges_new_and_old_style_joins
0
+    scoped_authors = Author.with_scope(:find => { :joins => :posts }) do
0
+      Author.find(:all, :select => 'DISTINCT authors.*', :joins => 'JOIN comments ON posts.id = comments.post_id', :conditions => 'comments.id = 1')
0
+    end
0
+    assert scoped_authors.include?(authors(:david))
0
+    assert !scoped_authors.include?(authors(:mary))
0
+    assert_equal 1, scoped_authors.size
0
+    assert_equal authors(:david).attributes, scoped_authors.first.attributes
0
+  end
0
+
0
   def test_scoped_count_include
0
     # with the include, will retrieve only developers for the given project
0
     Developer.with_scope(:find => { :include => :projects }) do
0
@@ -152,7 +193,7 @@ class MethodScopingTest < ActiveRecord::TestCase
0
 end
0
 
0
 class NestedScopingTest < ActiveRecord::TestCase
0
-  fixtures :developers, :projects, :comments, :posts
0
+  fixtures :authors, :developers, :projects, :comments, :posts
0
 
0
   def test_merge_options
0
     Developer.with_scope(:find => { :conditions => 'salary = 80000' }) do
0
@@ -357,6 +398,42 @@ class NestedScopingTest < ActiveRecord::TestCase
0
       assert_equal scoped_methods, Developer.instance_eval('current_scoped_methods')
0
     end
0
   end
0
+
0
+  def test_nested_scoped_find_merges_old_style_joins
0
+    scoped_authors = Author.with_scope(:find => { :joins => 'INNER JOIN posts ON authors.id = posts.author_id' }) do
0
+      Author.with_scope(:find => { :joins => 'INNER JOIN comments ON posts.id = comments.post_id' }) do
0
+        Author.find(:all, :select => 'DISTINCT authors.*', :conditions => 'comments.id = 1')
0
+      end
0
+    end
0
+    assert scoped_authors.include?(authors(:david))
0
+    assert !scoped_authors.include?(authors(:mary))
0
+    assert_equal 1, scoped_authors.size
0
+    assert_equal authors(:david).attributes, scoped_authors.first.attributes
0
+  end
0
+
0
+  def test_nested_scoped_find_merges_new_style_joins
0
+    scoped_authors = Author.with_scope(:find => { :joins => :posts }) do
0
+      Author.with_scope(:find => { :joins => :comments }) do
0
+        Author.find(:all, :select => 'DISTINCT authors.*', :conditions => 'comments.id = 1')
0
+      end
0
+    end
0
+    assert scoped_authors.include?(authors(:david))
0
+    assert !scoped_authors.include?(authors(:mary))
0
+    assert_equal 1, scoped_authors.size
0
+    assert_equal authors(:david).attributes, scoped_authors.first.attributes
0
+  end
0
+
0
+  def test_nested_scoped_find_merges_new_and_old_style_joins
0
+    scoped_authors = Author.with_scope(:find => { :joins => :posts }) do
0
+      Author.with_scope(:find => { :joins => 'INNER JOIN comments ON posts.id = comments.post_id' }) do
0
+        Author.find(:all, :select => 'DISTINCT authors.*', :joins => '', :conditions => 'comments.id = 1')
0
+      end
0
+    end
0
+    assert scoped_authors.include?(authors(:david))
0
+    assert !scoped_authors.include?(authors(:mary))
0
+    assert_equal 1, scoped_authors.size
0
+    assert_equal authors(:david).attributes, scoped_authors.first.attributes
0
+  end
0
 end
0
 
0
 class HasManyScopingTest< ActiveRecord::TestCase

Comments