From e0162aebb9ce3e02573fcfde5cfe97c664e54a7b Mon Sep 17 00:00:00 2001 From: Jim Menard Date: Fri, 16 Jan 2009 14:41:53 -0500 Subject: [PATCH] Level 1 support: - Added PK factory support to the db class. Documented it in the README. - Mongo#db now takes an options hash (right now, for :strict and :pk) and passes it in to the DB constructor. --- README.rdoc | 53 +++++++++++++++++++++++++++++++++++++++++++++- lib/mongo/db.rb | 34 +++++++++++++++++++++++++---- lib/mongo/mongo.rb | 7 +++--- tests/test_db.rb | 28 ++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 8 deletions(-) diff --git a/README.rdoc b/README.rdoc index 7e67d81..ac0e121 100644 --- a/README.rdoc +++ b/README.rdoc @@ -63,7 +63,58 @@ read from Mongo will have their character encodings set to UTF-8. When used with Ruby 1.8, the bytes in each string are written to and read from Mongo as-is. If the string is ASCII all is well, because ASCII is a subset of -UTF-8. If the string is not ASCII then it may not be a well-formed UTF-8 string. +UTF-8. If the string is not ASCII then it may not be a well-formed UTF-8 +string. + +== The DB class + +=== Primary Key Factories + +A basic Mongo driver is not responsible for creating primary keys or knowing +how to interpret them. You can tell the Ruby Mongo driver how to create +primary keys by passing in the :pk option to the Mongo#db method. + + include XGen::Mongo::Driver + db = Mongo.new.db('dbname', :pk => MyPKFactory) + +A primary key factory object must respond to :create_pk, which should take a +hash and return a hash which merges the original hash with any primary key +fields the factory wishes to inject. NOTE: if the object already has a primary +key, the factory should not inject a new key; this means that the object is +being used in a repsert but it already exists. The idea here is that when ever +a record is inserted, the :pk object's +create_pk+ method will be called and +the new hash returned will be inserted. + +Here is a sample primary key factory, taken from the tests: + + class TestPKFactory + def create_pk(row) + row['_id'] ||= XGen::Mongo::Driver::ObjectID.new + row + end + end + +A database's PK factory object may not be changed. Right now, it may only be +specified when you obtain the db object. + +=== Strict mode + +Each database has an optional strict mode. If strict mode is on, then asking +for a collection that does not exist will raise an error, as will asking to +create a collection that already exists. Note that both these operations are +completely harmless; strict mode is a programmer convenience only. + +To turn on strict mode, either pass in :strict => true when obtaining a DB +object or call the :strict= method: + + db = XGen::Mongo::Driver::Mongo.new.db('dbname', :strict => true) + # I'm feeling lax + db.strict = false + # No, I'm not! + db.strict = true + +The method DB#strict? returns the current value of that flag. + = Testing diff --git a/lib/mongo/db.rb b/lib/mongo/db.rb index 84a5d9e..e581904 100644 --- a/lib/mongo/db.rb +++ b/lib/mongo/db.rb @@ -59,17 +59,40 @@ module XGen # The database's socket. For internal (and Cursor) use only. attr_reader :socket + # A primary key factory object (or +nil+). See the README.doc file or + # DB#new for details. + attr_reader :pk_factory + # db_name :: The database name # # nodes :: An array of [host, port] pairs. # + # options :: A hash of options. + # + # Options: + # + # :strict :: If true, collections must exist to be accessed and must + # not exist to be created. See #collection and + # #create_collection. + # + # :pk :: A primary key factory object that must respond to :create_pk, + # which should take a hash and return a hash which merges the + # original hash with any primary key fields the factory wishes + # to inject. (NOTE: if the object already has a primary key, + # the factory should not inject a new key; this means that the + # object is being used in a repsert but it already exists.) The + # idea here is that when ever a record is inserted, the :pk + # object's +create_pk+ method will be called and the new hash + # returned will be inserted. + # # When a DB object first connects, it tries the first node. If that # fails, it keeps trying to connect to the remaining nodes until it # sucessfully connects. - def initialize(db_name, nodes) + def initialize(db_name, nodes, options={}) raise "Invalid DB name" if !db_name || (db_name && db_name.length > 0 && db_name.include?(".")) @name, @nodes = db_name, nodes - @strict = false + @strict = options[:strict] + @pk_factory = options[:pk] @semaphore = Object.new @semaphore.extend Mutex_m connect_to_first_available_host @@ -252,8 +275,8 @@ module XGen # applying +obj+ as an update. If no match, inserts (???). Normally # called by Collection#repsert. def repsert_in_db(collection_name, selector, obj) - # TODO if PKInjector, inject @semaphore.synchronize { + obj = @pk_factory.create_pk(obj) if @pk_factory send_to_db(UpdateMessage.new(@name, collection_name, selector, obj, true)) obj } @@ -325,7 +348,10 @@ module XGen # Collection#insert. def insert_into_db(collection_name, objects) @semaphore.synchronize { - objects.each { |o| send_to_db(InsertMessage.new(@name, collection_name, o)) } + objects.each { |o| + o = @pk_factory.create_pk(o) if @pk_factory + send_to_db(InsertMessage.new(@name, collection_name, o)) + } } end diff --git a/lib/mongo/mongo.rb b/lib/mongo/mongo.rb index 3b948dc..0d57cf3 100644 --- a/lib/mongo/mongo.rb +++ b/lib/mongo/mongo.rb @@ -52,9 +52,10 @@ module XGen end end - # Return the XGen::Mongo::Driver::DB named +db_name+. - def db(db_name) - XGen::Mongo::Driver::DB.new(db_name, @nodes) + # Return the XGen::Mongo::Driver::DB named +db_name+. See DB#new for + # +options+. + def db(db_name, options={}) + XGen::Mongo::Driver::DB.new(db_name, @nodes, options) end # Returns a hash containing database names as keys and disk space for diff --git a/tests/test_db.rb b/tests/test_db.rb index e7ef926..adac947 100644 --- a/tests/test_db.rb +++ b/tests/test_db.rb @@ -2,6 +2,13 @@ $LOAD_PATH[0,0] = File.join(File.dirname(__FILE__), '..', 'lib') require 'mongo' require 'test/unit' +class TestPKFactory + def create_pk(row) + row['_id'] ||= XGen::Mongo::Driver::ObjectID.new + row + end +end + # NOTE: assumes Mongo is running class DBTest < Test::Unit::TestCase @@ -48,4 +55,25 @@ class DBTest < Test::Unit::TestCase assert @db.connected? end + def test_pk_factory + db = Mongo.new(@host, @port).db('ruby-mongo-test', :pk => TestPKFactory.new) + coll = db.collection('test') + coll.clear + + coll.insert('name' => 'Fred') + row = coll.find({'name' => 'Fred'}, :limit => 1).next_object + assert_not_nil row + assert_equal 'Fred', row['name'] + assert_kind_of XGen::Mongo::Driver::ObjectID, row['_id'] + + oid = XGen::Mongo::Driver::ObjectID.new + coll.insert('_id' => oid, 'name' => 'Barney') + row = coll.find({'name' => 'Barney'}, :limit => 1).next_object + assert_not_nil row + assert_equal 'Barney', row['name'] + assert_equal oid, row['_id'] + + coll.clear + end + end