public
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
Add :having option to find, to use in combination with grouped finds. Also added 
to has_many and has_and_belongs_to_many associations.

Signed-off-by: Michael Koziarski <michael@koziarski.com>
[#1028 state:committed]
miloops (author)
Fri Nov 21 14:20:33 -0800 2008
NZKoz (committer)
Mon Dec 01 11:22:31 -0800 2008
commit  97403ad5fdfcdfb2110c6f8fd0ebf43b7afc4859
tree    77998cf59255d49323d7f7063025ca895e162f62
parent  0c4ba90aa1ea6a8d386c724a55a31e63a13c46ab
...
1
2
 
 
3
4
5
...
1
2
3
4
5
6
7
0
@@ -1,5 +1,7 @@
0
 *2.3.0/3.0*
0
 
0
+* Add :having as a key to find and the relevant associations.  [miloops]
0
+
0
 * Added default_scope to Base #1381 [PaweÅ‚ Kondzior]. Example:
0
 
0
     class Person < ActiveRecord::Base
...
724
725
726
 
 
727
728
729
...
1181
1182
1183
 
 
1184
1185
1186
...
1553
1554
1555
1556
 
1557
1558
1559
...
1609
1610
1611
1612
 
1613
1614
1615
...
1658
1659
1660
1661
 
1662
1663
1664
...
1714
1715
1716
1717
 
1718
1719
1720
...
724
725
726
727
728
729
730
731
...
1183
1184
1185
1186
1187
1188
1189
1190
...
1557
1558
1559
 
1560
1561
1562
1563
...
1613
1614
1615
 
1616
1617
1618
1619
...
1662
1663
1664
 
1665
1666
1667
1668
...
1718
1719
1720
 
1721
1722
1723
1724
0
@@ -724,6 +724,8 @@ module ActiveRecord
0
       #   Specify second-order associations that should be eager loaded when the collection is loaded.
0
       # [:group]
0
       #   An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
0
+      # [:having]
0
+      #   Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
0
       # [:limit]
0
       #   An integer determining the limit on the number of rows that should be returned.
0
       # [:offset]
0
@@ -1181,6 +1183,8 @@ module ActiveRecord
0
       #   Specify second-order associations that should be eager loaded when the collection is loaded.
0
       # [:group]
0
       #   An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
0
+      # [:having]
0
+      #   Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
0
       # [:limit]
0
       #   An integer determining the limit on the number of rows that should be returned.
0
       # [:offset]
0
@@ -1553,7 +1557,7 @@ module ActiveRecord
0
         @@valid_keys_for_has_many_association = [
0
           :class_name, :table_name, :foreign_key, :primary_key,
0
           :dependent,
0
-          :select, :conditions, :include, :order, :group, :limit, :offset,
0
+          :select, :conditions, :include, :order, :group, :having, :limit, :offset,
0
           :as, :through, :source, :source_type,
0
           :uniq,
0
           :finder_sql, :counter_sql,
0
@@ -1609,7 +1613,7 @@ module ActiveRecord
0
         mattr_accessor :valid_keys_for_has_and_belongs_to_many_association
0
         @@valid_keys_for_has_and_belongs_to_many_association = [
0
           :class_name, :table_name, :join_table, :foreign_key, :association_foreign_key,
0
-          :select, :conditions, :include, :order, :group, :limit, :offset,
0
+          :select, :conditions, :include, :order, :group, :having, :limit, :offset,
0
           :uniq,
0
           :finder_sql, :counter_sql, :delete_sql, :insert_sql,
0
           :before_add, :after_add, :before_remove, :after_remove,
0
@@ -1658,7 +1662,7 @@ module ActiveRecord
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
-          add_group!(sql, options[:group], scope)
0
+          add_group!(sql, options[:group], options[:having], scope)
0
           add_order!(sql, options[:order], scope)
0
           add_limit!(sql, options, scope) if using_limitable_reflections?(join_dependency.reflections)
0
           add_lock!(sql, options, scope)
0
@@ -1714,7 +1718,7 @@ module ActiveRecord
0
           end
0
 
0
           add_conditions!(sql, options[:conditions], scope)
0
-          add_group!(sql, options[:group], scope)
0
+          add_group!(sql, options[:group], options[:having], scope)
0
 
0
           if order && is_distinct
0
             connection.add_order_by_for_association_limiting!(sql, :order => order)
...
188
189
190
 
191
192
193
...
188
189
190
191
192
193
194
0
@@ -188,6 +188,7 @@ module ActiveRecord
0
         def merge_options_from_reflection!(options)
0
           options.reverse_merge!(
0
             :group   => @reflection.options[:group],
0
+            :having  => @reflection.options[:having],
0
             :limit   => @reflection.options[:limit],
0
             :offset  => @reflection.options[:offset],
0
             :joins   => @reflection.options[:joins],
...
521
522
523
 
524
525
526
...
1632
1633
1634
1635
 
1636
1637
1638
...
1688
1689
1690
1691
 
1692
1693
 
1694
1695
1696
1697
 
1698
1699
1700
...
2259
2260
2261
2262
 
2263
2264
2265
...
521
522
523
524
525
526
527
...
1633
1634
1635
 
1636
1637
1638
1639
...
1689
1690
1691
 
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
...
2262
2263
2264
 
2265
2266
2267
2268
0
@@ -521,6 +521,7 @@ module ActiveRecord #:nodoc:
0
       # * <tt>:conditions</tt> - An SQL fragment like "administrator = 1", <tt>[ "user_name = ?", username ]</tt>, or <tt>["user_name = :user_name", { :user_name => user_name }]</tt>. See conditions in the intro.
0
       # * <tt>:order</tt> - An SQL fragment like "created_at DESC, name".
0
       # * <tt>:group</tt> - An attribute name by which the result should be grouped. Uses the <tt>GROUP BY</tt> SQL-clause.
0
+      # * <tt>:having</tt> - Combined with +:group+ this can be used to filter the records that a <tt>GROUP BY</tt> returns. Uses the <tt>HAVING</tt> SQL-clause.
0
       # * <tt>:limit</tt> - An integer determining the limit on the number of rows that should be returned.
0
       # * <tt>:offset</tt> - An integer determining the offset from where the rows should be fetched. So at 5, it would skip rows 0 through 4.
0
       # * <tt>:joins</tt> - Either an SQL fragment for additional joins like "LEFT JOIN comments ON comments.post_id = id" (rarely needed)
0
@@ -1632,7 +1633,7 @@ module ActiveRecord #:nodoc:
0
           add_joins!(sql, options[:joins], scope)
0
           add_conditions!(sql, options[:conditions], scope)
0
 
0
-          add_group!(sql, options[:group], scope)
0
+          add_group!(sql, options[:group], options[:having], scope)
0
           add_order!(sql, options[:order], scope)
0
           add_limit!(sql, options, scope)
0
           add_lock!(sql, options, scope)
0
@@ -1688,13 +1689,15 @@ module ActiveRecord #:nodoc:
0
           end
0
         end
0
 
0
-        def add_group!(sql, group, scope = :auto)
0
+        def add_group!(sql, group, having, scope = :auto)
0
           if group
0
             sql << " GROUP BY #{group}"
0
+            sql << " HAVING #{having}" if having
0
           else
0
             scope = scope(:find) if :auto == scope
0
             if scope && (scoped_group = scope[:group])
0
               sql << " GROUP BY #{scoped_group}"
0
+              sql << " HAVING #{scoped_having}" if (scoped_having = scope[:having])
0
             end
0
           end
0
         end
0
@@ -2259,7 +2262,7 @@ module ActiveRecord #:nodoc:
0
         end
0
 
0
         VALID_FIND_OPTIONS = [ :conditions, :include, :joins, :limit, :offset,
0
-                               :order, :select, :readonly, :group, :from, :lock ]
0
+                               :order, :select, :readonly, :group, :having, :from, :lock ]
0
 
0
         def validate_find_options(options) #:nodoc:
0
           options.assert_valid_keys(VALID_FIND_OPTIONS)
...
658
659
660
 
 
 
 
 
661
662
663
...
658
659
660
661
662
663
664
665
666
667
668
0
@@ -658,6 +658,11 @@ class HasAndBelongsToManyAssociationsTest < ActiveRecord::TestCase
0
     assert_equal 1, categories(:technology).posts_gruoped_by_title.size
0
   end
0
 
0
+  def test_find_scoped_grouped_having
0
+    assert_equal 2, projects(:active_record).well_payed_salary_groups.size
0
+    assert projects(:active_record).well_payed_salary_groups.all? { |g| g.salary > 10000 }
0
+  end
0
+
0
   def test_get_ids
0
     assert_equal projects(:active_record, :action_controller).map(&:id).sort, developers(:david).project_ids.sort
0
     assert_equal [projects(:active_record).id], developers(:jamis).project_ids
...
255
256
257
 
 
 
 
 
258
259
260
...
255
256
257
258
259
260
261
262
263
264
265
0
@@ -255,6 +255,11 @@ class HasManyAssociationsTest < ActiveRecord::TestCase
0
     assert_equal 2, companies(:first_firm).clients_grouped_by_name.length
0
   end
0
 
0
+  def test_find_scoped_grouped_having
0
+    assert_equal 1, authors(:david).popular_grouped_posts.length
0
+    assert_equal 0, authors(:mary).popular_grouped_posts.length
0
+  end
0
+
0
   def test_adding
0
     force_signal37_to_load_all_clients_of_firm
0
     natural = Client.new("name" => "Natural Company")
...
175
176
177
 
 
 
 
 
 
 
178
179
180
...
175
176
177
178
179
180
181
182
183
184
185
186
187
0
@@ -175,6 +175,13 @@ class FinderTest < ActiveRecord::TestCase
0
     assert_equal 4, developers.map(&:salary).uniq.size
0
   end
0
 
0
+  def test_find_with_group_and_having
0
+    developers =  Developer.find(:all, :group => "salary", :having => "sum(salary) >  10000", :select => "salary")
0
+    assert_equal 3, developers.size
0
+    assert_equal 3, developers.map(&:salary).uniq.size
0
+    assert developers.all? { |developer|  developer.salary > 10000 }
0
+  end
0
+
0
   def test_find_with_entire_select_statement
0
     topics = Topic.find_by_sql "SELECT * FROM topics WHERE author_name = 'Mary'"
0
 
...
1
2
3
 
4
5
6
...
1
2
3
4
5
6
7
0
@@ -1,6 +1,7 @@
0
 class Author < ActiveRecord::Base
0
   has_many :posts
0
   has_many :posts_with_comments, :include => :comments, :class_name => "Post"
0
+  has_many :popular_grouped_posts, :include => :comments, :class_name => "Post", :group => "type", :having => "SUM(comments_count) > 1", :select => "type"
0
   has_many :posts_with_comments_sorted_by_comment_id, :include => :comments, :class_name => "Post", :order => 'comments.id'
0
   has_many :posts_sorted_by_id_limited, :class_name => "Post", :order => 'posts.id', :limit => 1
0
   has_many :posts_with_categories, :include => :categories, :class_name => "Post"
...
14
15
16
 
17
18
19
...
14
15
16
17
18
19
20
0
@@ -14,6 +14,7 @@ class Category < ActiveRecord::Base
0
                           :class_name => 'Post',
0
                           :conditions => { :title => 'Yet Another Testing Title' }
0
 
0
+  has_and_belongs_to_many :popular_grouped_posts, :class_name => "Post", :group => "posts.type", :having => "sum(comments.post_id) > 2", :include => :comments
0
   has_and_belongs_to_many :posts_gruoped_by_title, :class_name => "Post", :group => "title", :select => "title"
0
 
0
   def self.what_are_you
...
13
14
15
 
16
17
18
...
13
14
15
16
17
18
19
0
@@ -13,6 +13,7 @@ class Project < ActiveRecord::Base
0
                             :after_add => Proc.new {|o, r| o.developers_log << "after_adding#{r.id || '<new>'}"},
0
                             :before_remove => Proc.new {|o, r| o.developers_log << "before_removing#{r.id}"},
0
                             :after_remove => Proc.new {|o, r| o.developers_log << "after_removing#{r.id}"}
0
+  has_and_belongs_to_many :well_payed_salary_groups, :class_name => "Developer", :group => "salary", :having => "SUM(salary) > 10000", :select => "SUM(salary) as salary"
0
 
0
   attr_accessor :developers_log
0
 

Comments