Permalink
Browse files

Add `group_by` to `ActiveRecord::FinderMethods`

Enables collecting of records into sets, grouped by distinct values for the
specified `field`. Leverages `ActiveRecord::Relation` to be far more
efficient than `Enumerable#group_by` when selecting based on a column
name.

    Example:

        User.group_by('role')
        # => {
        #   "normal" => #<ActiveRecord::Relation [...]>,
        #   "admin" => #<ActiveRecord::Relation [...]>
        # }
  • Loading branch information...
1 parent efaecad commit eacb28e696f0e688e8b43f196d7903f08eff7a3e @afeld committed May 2, 2013
@@ -1,3 +1,12 @@
+* Add `ActiveRecord::FinderMethods#group_by` method, which collects records into sets, grouped by distinct values for the specified `field`. This patch leverages `ActiveRecord::Relation` to be far more efficient than `Enumerable#group_by` when selecting based on a column name.
+
+ Example:
+
+ User.group_by('role')
+ # => {"normal" => #<ActiveRecord::Relation [...]>, "admin" => #<ActiveRecord::Relation [...]>}
+
+ *Aidan Feldman*
+
* Handle aliased attributes in ActiveRecord::Relation.
When using symbol keys, ActiveRecord will now translate aliased attribute names to the actual column name used in the database:
@@ -176,6 +176,28 @@ def exists?(conditions = :none)
false
end
+ # Collect records into sets, grouped by distinct values for the specified +field+.
+ #
+ # User.select([:id, :name])
+ # => [#<User id: 1, name: "Oscar">, #<User id: 2, name: "Oscar">, #<User id: 3, name: "Foo">
+ #
+ # User.group_by(:name)
+ # => {"Foo" => #<ActiveRecord::Relation [...]>, "Oscar" => #<ActiveRecord::Relation [...]>}
+ def group_by(*args, &block)
+ field = args[0]
+ if field.nil? || block_given?
+ super(&block)
+ else
+ result = {}
+ self.select(field).distinct.each do |item|
+ value = item[field]
+ result[value] = self.where(field => value)
+ end
+
+ result
+ end
+ end
+
# This method is called whenever no records are found with either a single
# id or multiple ids and raises a +ActiveRecord::RecordNotFound+ exception.
#
@@ -849,6 +849,31 @@ def test_finder_with_offset_string
assert_nothing_raised(ActiveRecord::StatementInvalid) { Topic.all.merge!(:offset => "3").to_a }
end
+ def test_group_by_with_symbol
+ groups = Company.all.group_by(:type)
+ assert_kind_of Hash, groups
+
+ keys = groups.keys
+ assert_equal 5, keys.size
+ assert_equal [nil, 'Client', 'DependentFirm', 'ExclusivelyDependentFirm', 'Firm'].to_set, keys.to_set
+
+ assert_equal [2, 3, 5, 10], groups['Client'].map(&:id).sort
+ assert_equal [1, 4], groups['Firm'].map(&:id).sort
+ assert_equal nil, groups['Nonexistent']
+ end
+
+ def test_group_by_with_block
+ groups = Company.all.group_by { |c| c.firm_id }
+
+ group = groups[4]
+ assert_kind_of Array, group
+ assert_equal 2, group.size
+ end
+
+ def test_group_by_with_no_args
+ assert_kind_of Enumerable, Company.all.group_by
+ end
+
protected
def bind(statement, *vars)
if vars.first.is_a?(Hash)

0 comments on commit eacb28e

Please sign in to comment.