diff --git a/lib/mongo.rb b/lib/mongo.rb index 12aec33..452396f 100644 --- a/lib/mongo.rb +++ b/lib/mongo.rb @@ -27,9 +27,9 @@ end require 'bson' +require 'mongo/util/conversions' require 'mongo/util/support' require 'mongo/util/core_ext' -require 'mongo/util/conversions' require 'mongo/util/server_version' require 'mongo/collection' diff --git a/lib/mongo/collection.rb b/lib/mongo/collection.rb index 14c5e74..a471940 100644 --- a/lib/mongo/collection.rb +++ b/lib/mongo/collection.rb @@ -405,7 +405,6 @@ module Mongo # Note: calling drop_indexes with no args will drop them all. @db.drop_index(@name, '*') - end # Drop the entire collection. USE WITH CAUTION. @@ -413,6 +412,30 @@ module Mongo @db.drop_collection(@name) end + + # Atomically update and return a document using MongoDB's findAndModify command. (MongoDB > 1.3.0) + # + # @option opts [Hash] :update (nil) the update operation to perform on the matched document. + # @option opts [Hash] :query ({}) a query selector document for matching the desired document. + # @option opts [Array, String, OrderedHash] :sort ({}) specify a sort option for the query using any + # of the sort options available for Cursor#sort. Sort order is important if the query will be matching + # multiple documents since only the first matching document will be updated and returned. + # @option opts [Boolean] :remove (false) If true, removes the the returned document from the collection. + # @option opts [Boolean] :new (false) If true, returns the updated document; otherwise, returns the document + # prior to update. + # + # @return [Hash] the matched document. + # + # @core mapreduce map_reduce-instance_method + def find_and_modify(opts={}) + cmd = OrderedHash.new + cmd[:findandmodify] = @name + cmd.merge!(opts) + cmd[:sort] = Mongo::Support.format_order_clause(opts[:sort]) if opts[:sort] + + @db.command(cmd, false, true)['value'] + end + # Perform a map/reduce operation on the current collection. # # @param [String, BSON::Code] map a map function, written in JavaScript. diff --git a/lib/mongo/cursor.rb b/lib/mongo/cursor.rb index 6137794..ed9b417 100644 --- a/lib/mongo/cursor.rb +++ b/lib/mongo/cursor.rb @@ -365,7 +365,7 @@ module Mongo return @selector if @selector.has_key?('$query') spec = OrderedHash.new spec['$query'] = @selector - spec['$orderby'] = formatted_order_clause if @order + spec['$orderby'] = Mongo::Support.format_order_clause(@order) if @order spec['$hint'] = @hint if @hint && @hint.length > 0 spec['$explain'] = true if @explain spec['$snapshot'] = true if @snapshot @@ -377,16 +377,6 @@ module Mongo @order || @explain || @hint || @snapshot end - def formatted_order_clause - case @order - when String, Symbol then string_as_sort_parameters(@order) - when Array then array_as_sort_parameters(@order) - else - raise InvalidSortValueError, "Illegal sort clause, '#{@order.class.name}'; must be of the form " + - "[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]" - end - end - def to_s "DBResponse(flags=#@result_flags, cursor_id=#@cursor_id, start=#@starting_from)" end diff --git a/lib/mongo/util/support.rb b/lib/mongo/util/support.rb index f361d7e..2b9dc66 100644 --- a/lib/mongo/util/support.rb +++ b/lib/mongo/util/support.rb @@ -18,6 +18,7 @@ require 'digest/md5' module Mongo module Support + include Mongo::Conversions extend self # Generate an MD5 for authentication. @@ -55,5 +56,15 @@ module Mongo raise Mongo::InvalidNSName, "database name cannot be the empty string" if db_name.empty? db_name end + + def format_order_clause(order) + case order + when String, Symbol then string_as_sort_parameters(order) + when Array then array_as_sort_parameters(order) + else + raise InvalidSortValueError, "Illegal sort clause, '#{order.class.name}'; must be of the form " + + "[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]" + end + end end end diff --git a/test/collection_test.rb b/test/collection_test.rb index 011acd0..75fbc80 100644 --- a/test/collection_test.rb +++ b/test/collection_test.rb @@ -7,7 +7,7 @@ class TestCollection < Test::Unit::TestCase @@version = @@connection.server_version def setup - @@test.drop() + @@test.remove end def test_optional_pk_factory @@ -161,6 +161,7 @@ class TestCollection < Test::Unit::TestCase assert_raise OperationFailure do @@test.update({}, {"$inc" => {"x" => 1}}, :safe => true) end + @@test.drop end else def test_safe_update @@ -176,6 +177,7 @@ class TestCollection < Test::Unit::TestCase assert_raise OperationFailure do @@test.update({}, {"x" => 10}, :safe => true) end + @@test.drop end end @@ -188,6 +190,7 @@ class TestCollection < Test::Unit::TestCase assert_raise OperationFailure do @@test.save({"hello" => "world"}, :safe => true) end + @@test.drop end def test_mocked_safe_remove @@ -374,6 +377,28 @@ class TestCollection < Test::Unit::TestCase end end + if @@version > "1.3.0" + def test_find_and_modify + @@test << { :a => 1, :processed => false } + @@test << { :a => 2, :processed => false } + @@test << { :a => 3, :processed => false } + + @@test.find_and_modify(:query => {}, :sort => [['a', -1]], :update => {"$set" => {:processed => true}}) + + assert @@test.find_one({:a => 3})['processed'] + end + + def test_find_and_modify_with_invalid_options + @@test << { :a => 1, :processed => false } + @@test << { :a => 2, :processed => false } + @@test << { :a => 3, :processed => false } + + assert_raise Mongo::OperationFailure do + @@test.find_and_modify(:blimey => {}) + end + end + end + def test_saving_dates_pre_epoch begin @@test.save({'date' => Time.utc(1600)})