RUBY-222 Collection#group gets a new, reasonable API
This commit is contained in:
parent
756ccfe877
commit
65f59ba2d6
|
@ -47,5 +47,9 @@ module BSON
|
||||||
"<BSON::Code:#{object_id} @data=\"#{@code}\" @scope=\"#{@scope.inspect}\">"
|
"<BSON::Code:#{object_id} @data=\"#{@code}\" @scope=\"#{@scope.inspect}\">"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def to_bson_code
|
||||||
|
self
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -556,17 +556,28 @@ module Mongo
|
||||||
|
|
||||||
# Perform a group aggregation.
|
# Perform a group aggregation.
|
||||||
#
|
#
|
||||||
# @param [Array, String, BSON::Code, Nil] :key either 1) an array of fields to group by,
|
# @param [Hash] opts the options for this group operation. The minimum required are :initial
|
||||||
# 2) a javascript function to generate the key object, or 3) nil.
|
# and :reduce.
|
||||||
# @param [Hash] condition an optional document specifying a query to limit the documents over which group is run.
|
|
||||||
# @param [Hash] initial initial value of the aggregation counter object
|
|
||||||
# @param [String, BSON::Code] reduce aggregation function, in JavaScript
|
|
||||||
# @param [String, BSON::Code] finalize :: optional. a JavaScript function that receives and modifies
|
|
||||||
# each of the resultant grouped objects. Available only when group is run
|
|
||||||
# with command set to true.
|
|
||||||
#
|
#
|
||||||
# @return [Array] the grouped items.
|
# @option opts [Array, String, Symbol] :key (nil) Either the name of a field or a list of fields to group by (optional).
|
||||||
def group(key, condition, initial, reduce, finalize=nil)
|
# @option opts [String, BSON::Code] :keyf (nil) A JavaScript function to be used to generate the grouping keys (optional).
|
||||||
|
# @option opts [String, BSON::Code] :cond ({}) A document specifying a query for filtering the documents over
|
||||||
|
# which the aggregation is run (optional).
|
||||||
|
# @option opts [Hash] :initial the initial value of the aggregation counter object (required).
|
||||||
|
# @option opts [String, BSON::Code] :reduce (nil) a JavaScript aggregation function (required).
|
||||||
|
# @option opts [String, BSON::Code] :finalize (nil) a JavaScript function that receives and modifies
|
||||||
|
# each of the resultant grouped objects. Available only when group is run with command
|
||||||
|
# set to true.
|
||||||
|
#
|
||||||
|
# @return [Array] the command response consisting of grouped items.
|
||||||
|
def group(key, condition={}, initial={}, reduce=nil, finalize=nil)
|
||||||
|
if key.is_a?(Hash)
|
||||||
|
return new_group(key)
|
||||||
|
else
|
||||||
|
warn "Collection#group no longer take a list of paramters. This usage is deprecated." +
|
||||||
|
"Check out the new API at http://api.mongodb.org/ruby/current/Mongo/Collection.html#group-instance_method"
|
||||||
|
end
|
||||||
|
|
||||||
reduce = BSON::Code.new(reduce) unless reduce.is_a?(BSON::Code)
|
reduce = BSON::Code.new(reduce) unless reduce.is_a?(BSON::Code)
|
||||||
|
|
||||||
group_command = {
|
group_command = {
|
||||||
|
@ -578,6 +589,11 @@ module Mongo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if key.is_a?(Symbol)
|
||||||
|
raise MongoArgumentError, "Group takes either an array of fields to group by or a JavaScript function" +
|
||||||
|
"in the form of a String or BSON::Code."
|
||||||
|
end
|
||||||
|
|
||||||
unless key.nil?
|
unless key.nil?
|
||||||
if key.is_a? Array
|
if key.is_a? Array
|
||||||
key_type = "key"
|
key_type = "key"
|
||||||
|
@ -605,6 +621,48 @@ module Mongo
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def new_group(opts={})
|
||||||
|
reduce = opts[:reduce]
|
||||||
|
finalize = opts[:finalize]
|
||||||
|
cond = opts.fetch(:cond, {})
|
||||||
|
initial = opts[:initial]
|
||||||
|
|
||||||
|
if !(reduce && initial)
|
||||||
|
raise MongoArgumentError, "Group requires at minimum values for initial and reduce."
|
||||||
|
end
|
||||||
|
|
||||||
|
cmd = {
|
||||||
|
"group" => {
|
||||||
|
"ns" => @name,
|
||||||
|
"$reduce" => reduce.to_bson_code,
|
||||||
|
"cond" => cond,
|
||||||
|
"initial" => initial
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if finalize
|
||||||
|
cmd['group']['finalize'] = finalize.to_bson_code
|
||||||
|
end
|
||||||
|
|
||||||
|
if key = opts[:key]
|
||||||
|
if key.is_a?(String) || key.is_a?(Symbol)
|
||||||
|
key = [key]
|
||||||
|
end
|
||||||
|
key_value = {}
|
||||||
|
key.each { |k| key_value[k] = 1 }
|
||||||
|
cmd["group"]["key"] = key_value
|
||||||
|
elsif keyf = opts[:keyf]
|
||||||
|
cmd["group"]["$keyf"] = keyf.to_bson_code
|
||||||
|
end
|
||||||
|
|
||||||
|
result = @db.command(cmd)
|
||||||
|
result["retval"]
|
||||||
|
end
|
||||||
|
|
||||||
|
public
|
||||||
|
|
||||||
# Return a list of distinct values for +key+ across all
|
# Return a list of distinct values for +key+ across all
|
||||||
# documents in the collection. The key may use dot notation
|
# documents in the collection. The key may use dot notation
|
||||||
# to reach into an embedded object.
|
# to reach into an embedded object.
|
||||||
|
|
|
@ -48,3 +48,13 @@ class Hash
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
#:nodoc:
|
||||||
|
class String
|
||||||
|
|
||||||
|
#:nodoc:
|
||||||
|
def to_bson_code
|
||||||
|
BSON::Code.new(self)
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
|
@ -626,15 +626,25 @@ class TestCollection < Test::Unit::TestCase
|
||||||
@reduce_function = "function (obj, prev) { prev.count += inc_value; }"
|
@reduce_function = "function (obj, prev) { prev.count += inc_value; }"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
should "fail if missing required options" do
|
||||||
|
assert_raise MongoArgumentError do
|
||||||
|
@@test.group(:initial => {})
|
||||||
|
end
|
||||||
|
|
||||||
|
assert_raise MongoArgumentError do
|
||||||
|
@@test.group(:reduce => "foo")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
should "group results using eval form" do
|
should "group results using eval form" do
|
||||||
assert_equal 1, @@test.group([], {}, @initial, Code.new(@reduce_function, {"inc_value" => 0.5}))[0]["count"]
|
assert_equal 1, @@test.group(:initial => @initial, :reduce => Code.new(@reduce_function, {"inc_value" => 0.5}))[0]["count"]
|
||||||
assert_equal 2, @@test.group([], {}, @initial, Code.new(@reduce_function, {"inc_value" => 1}))[0]["count"]
|
assert_equal 2, @@test.group(:initial => @initial, :reduce => Code.new(@reduce_function, {"inc_value" => 1}))[0]["count"]
|
||||||
assert_equal 4, @@test.group([], {}, @initial, Code.new(@reduce_function, {"inc_value" => 2}))[0]["count"]
|
assert_equal 4, @@test.group(:initial => @initial, :reduce => Code.new(@reduce_function, {"inc_value" => 2}))[0]["count"]
|
||||||
end
|
end
|
||||||
|
|
||||||
should "finalize grouped results" do
|
should "finalize grouped results" do
|
||||||
@finalize = "function(doc) {doc.f = doc.count + 200; }"
|
@finalize = "function(doc) {doc.f = doc.count + 200; }"
|
||||||
assert_equal 202, @@test.group([], {}, @initial, Code.new(@reduce_function, {"inc_value" => 1}), @finalize)[0]["f"]
|
assert_equal 202, @@test.group(:initial => @initial, :reduce => Code.new(@reduce_function, {"inc_value" => 1}), :finalize => @finalize)[0]["f"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -650,7 +660,7 @@ class TestCollection < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
should "group" do
|
should "group" do
|
||||||
result = @@test.group([:a], {}, @initial, @reduce_function, nil)
|
result = @@test.group(:key => :a, :initial => @initial, :reduce => @reduce_function)
|
||||||
assert result.all? { |r| r['count'] == 200 }
|
assert result.all? { |r| r['count'] == 200 }
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -669,10 +679,17 @@ class TestCollection < Test::Unit::TestCase
|
||||||
end
|
end
|
||||||
|
|
||||||
should "group results" do
|
should "group results" do
|
||||||
results = @@test.group(@keyf, {}, @initial, @reduce).sort {|a, b| a['count'] <=> b['count']}
|
results = @@test.group(:keyf => @keyf, :initial => @initial, :reduce => @reduce).sort {|a, b| a['count'] <=> b['count']}
|
||||||
assert results[0]['even'] && results[0]['count'] == 2.0
|
assert results[0]['even'] && results[0]['count'] == 2.0
|
||||||
assert results[1]['odd'] && results[1]['count'] == 3.0
|
assert results[1]['odd'] && results[1]['count'] == 3.0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
should "group filtered results" do
|
||||||
|
results = @@test.group(:keyf => @keyf, :cond => {:a => {'$ne' => 2}},
|
||||||
|
:initial => @initial, :reduce => @reduce).sort {|a, b| a['count'] <=> b['count']}
|
||||||
|
assert results[0]['even'] && results[0]['count'] == 1.0
|
||||||
|
assert results[1]['odd'] && results[1]['count'] == 3.0
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "A collection with two records" do
|
context "A collection with two records" do
|
||||||
|
|
Loading…
Reference in New Issue