Added transformer concept.

Can be passed to find/find_one, which in turn is passed to cursor. It is an optional block that makes it easier to turn documents that are returned into hashes.

cursor = collection.find({...}, :transformer => Proc.new { |doc| User.load(doc) })
cursor.next # returns instance of User instead of ordered hash

This will allow MongoMapper, ToyStore and other object mappers to take better advantage of Cursors. No more calling to_a and mapping to instances.
This commit is contained in:
John Nunemaker 2011-03-18 15:03:37 -04:00 committed by Kyle Banker
parent 5cdae46b56
commit 53ad43fedc
4 changed files with 62 additions and 4 deletions

View File

@ -157,6 +157,8 @@ module Mongo
# the normal cursor timeout behavior of the mongod process. When +false+, the returned cursor will never timeout. Note
# that disabling timeout will only work when #find is invoked with a block. This is to prevent any inadvertant failure to
# close the cursor, as the cursor is explicitly closed when block code finishes.
# @options opts [Block] :transformer optional block that can be handed to cursor for tranforming returned documents. Most valuable
# use is for converting hashes to instances of a class.
#
# @raise [ArgumentError]
# if timeout is set to false and find is not invoked in a block
@ -175,6 +177,7 @@ module Mongo
snapshot = opts.delete(:snapshot)
batch_size = opts.delete(:batch_size)
timeout = (opts.delete(:timeout) == false) ? false : true
transformer = opts.delete(:transformer)
if timeout == false && !block_given?
raise ArgumentError, "Collection#find must be invoked with a block when timeout is disabled."
@ -188,8 +191,18 @@ module Mongo
raise RuntimeError, "Unknown options [#{opts.inspect}]" unless opts.empty?
cursor = Cursor.new(self, :selector => selector, :fields => fields, :skip => skip, :limit => limit,
:order => sort, :hint => hint, :snapshot => snapshot, :timeout => timeout, :batch_size => batch_size)
cursor = Cursor.new(self, {
:selector => selector,
:fields => fields,
:skip => skip,
:limit => limit,
:order => sort,
:hint => hint,
:snapshot => snapshot,
:timeout => timeout,
:batch_size => batch_size,
:transformer => transformer,
})
if block_given?
yield cursor

View File

@ -23,7 +23,7 @@ module Mongo
attr_reader :collection, :selector, :fields,
:order, :hint, :snapshot, :timeout,
:full_collection_name
:full_collection_name, :transformer
# Create a new cursor.
#
@ -52,6 +52,7 @@ module Mongo
@tailable = opts[:tailable] || false
@closed = false
@query_run = false
@transformer = opts[:transformer]
batch_size(opts[:batch_size] || 0)
@full_collection_name = "#{@collection.db.name}.#{@collection.name}"
@ -86,7 +87,11 @@ module Mongo
raise OperationFailure, err
end
doc
if @transformer.nil?
doc
else
@transformer.call(doc) if doc
end
end
alias :next :next_document

View File

@ -592,6 +592,20 @@ class TestCollection < Test::Unit::TestCase
assert_equal 1, x
end
def test_find_with_transformer
klass = Struct.new(:id, :a)
transformer = Proc.new { |doc| klass.new(doc['_id'], doc['a']) }
cursor = @@test.find({}, :transformer => transformer)
assert_equal(transformer, cursor.transformer)
end
def test_find_one_with_transformer
klass = Struct.new(:id, :a)
transformer = Proc.new { |doc| klass.new(doc['_id'], doc['a']) }
id = @@test.insert('a' => 1)
doc = @@test.find_one(id, :transformer => transformer)
assert_instance_of(klass, doc)
end
def test_ensure_index
@@test.drop_indexes

View File

@ -454,4 +454,30 @@ class CursorTest < Test::Unit::TestCase
assert_equal 100, cursor.map {|doc| doc }.length
end
def test_transformer
transformer = Proc.new { |doc| doc }
cursor = Cursor.new(@@coll, :transformer => transformer)
assert_equal(transformer, cursor.transformer)
end
def test_instance_transformation_with_next
klass = Struct.new(:id, :a)
transformer = Proc.new { |doc| klass.new(doc['_id'], doc['a']) }
cursor = Cursor.new(@@coll, :transformer => transformer)
instance = cursor.next
assert_instance_of(klass, instance)
assert_instance_of(BSON::ObjectId, instance.id)
assert_equal(1, instance.a)
end
def test_instance_transformation_with_each
klass = Struct.new(:id, :a)
transformer = Proc.new { |doc| klass.new(doc['_id'], doc['a']) }
cursor = Cursor.new(@@coll, :transformer => transformer)
cursor.each do |instance|
assert_instance_of(klass, instance)
end
end
end