jonbell-mongo 1.3.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/LICENSE.txt +190 -0
  2. data/README.md +333 -0
  3. data/Rakefile +215 -0
  4. data/bin/mongo_console +21 -0
  5. data/docs/CREDITS.md +123 -0
  6. data/docs/FAQ.md +116 -0
  7. data/docs/GridFS.md +158 -0
  8. data/docs/HISTORY.md +263 -0
  9. data/docs/RELEASES.md +33 -0
  10. data/docs/REPLICA_SETS.md +72 -0
  11. data/docs/TUTORIAL.md +247 -0
  12. data/docs/WRITE_CONCERN.md +28 -0
  13. data/lib/mongo.rb +97 -0
  14. data/lib/mongo/collection.rb +895 -0
  15. data/lib/mongo/connection.rb +926 -0
  16. data/lib/mongo/cursor.rb +474 -0
  17. data/lib/mongo/db.rb +617 -0
  18. data/lib/mongo/exceptions.rb +71 -0
  19. data/lib/mongo/gridfs/grid.rb +107 -0
  20. data/lib/mongo/gridfs/grid_ext.rb +57 -0
  21. data/lib/mongo/gridfs/grid_file_system.rb +146 -0
  22. data/lib/mongo/gridfs/grid_io.rb +485 -0
  23. data/lib/mongo/gridfs/grid_io_fix.rb +38 -0
  24. data/lib/mongo/repl_set_connection.rb +356 -0
  25. data/lib/mongo/util/conversions.rb +89 -0
  26. data/lib/mongo/util/core_ext.rb +60 -0
  27. data/lib/mongo/util/pool.rb +177 -0
  28. data/lib/mongo/util/server_version.rb +71 -0
  29. data/lib/mongo/util/support.rb +82 -0
  30. data/lib/mongo/util/uri_parser.rb +185 -0
  31. data/mongo.gemspec +34 -0
  32. data/test/auxillary/1.4_features.rb +166 -0
  33. data/test/auxillary/authentication_test.rb +68 -0
  34. data/test/auxillary/autoreconnect_test.rb +41 -0
  35. data/test/auxillary/fork_test.rb +30 -0
  36. data/test/auxillary/repl_set_auth_test.rb +58 -0
  37. data/test/auxillary/slave_connection_test.rb +36 -0
  38. data/test/auxillary/threaded_authentication_test.rb +101 -0
  39. data/test/bson/binary_test.rb +15 -0
  40. data/test/bson/bson_test.rb +654 -0
  41. data/test/bson/byte_buffer_test.rb +208 -0
  42. data/test/bson/hash_with_indifferent_access_test.rb +38 -0
  43. data/test/bson/json_test.rb +17 -0
  44. data/test/bson/object_id_test.rb +154 -0
  45. data/test/bson/ordered_hash_test.rb +210 -0
  46. data/test/bson/timestamp_test.rb +24 -0
  47. data/test/collection_test.rb +910 -0
  48. data/test/connection_test.rb +324 -0
  49. data/test/conversions_test.rb +119 -0
  50. data/test/cursor_fail_test.rb +75 -0
  51. data/test/cursor_message_test.rb +43 -0
  52. data/test/cursor_test.rb +483 -0
  53. data/test/db_api_test.rb +738 -0
  54. data/test/db_connection_test.rb +15 -0
  55. data/test/db_test.rb +315 -0
  56. data/test/grid_file_system_test.rb +259 -0
  57. data/test/grid_io_test.rb +209 -0
  58. data/test/grid_test.rb +258 -0
  59. data/test/load/thin/load.rb +24 -0
  60. data/test/load/unicorn/load.rb +23 -0
  61. data/test/replica_sets/connect_test.rb +112 -0
  62. data/test/replica_sets/connection_string_test.rb +32 -0
  63. data/test/replica_sets/count_test.rb +35 -0
  64. data/test/replica_sets/insert_test.rb +53 -0
  65. data/test/replica_sets/pooled_insert_test.rb +55 -0
  66. data/test/replica_sets/query_secondaries.rb +108 -0
  67. data/test/replica_sets/query_test.rb +51 -0
  68. data/test/replica_sets/replication_ack_test.rb +66 -0
  69. data/test/replica_sets/rs_test_helper.rb +27 -0
  70. data/test/safe_test.rb +68 -0
  71. data/test/support/hash_with_indifferent_access.rb +186 -0
  72. data/test/support/keys.rb +45 -0
  73. data/test/support_test.rb +18 -0
  74. data/test/test_helper.rb +102 -0
  75. data/test/threading/threading_with_large_pool_test.rb +90 -0
  76. data/test/threading_test.rb +87 -0
  77. data/test/tools/auth_repl_set_manager.rb +14 -0
  78. data/test/tools/repl_set_manager.rb +266 -0
  79. data/test/unit/collection_test.rb +130 -0
  80. data/test/unit/connection_test.rb +85 -0
  81. data/test/unit/cursor_test.rb +109 -0
  82. data/test/unit/db_test.rb +94 -0
  83. data/test/unit/grid_test.rb +49 -0
  84. data/test/unit/pool_test.rb +9 -0
  85. data/test/unit/repl_set_connection_test.rb +59 -0
  86. data/test/unit/safe_test.rb +125 -0
  87. data/test/uri_test.rb +91 -0
  88. metadata +224 -0
@@ -0,0 +1,60 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+
19
+ #:nodoc:
20
+ class Object
21
+
22
+ #:nodoc:
23
+ def tap
24
+ yield self
25
+ self
26
+ end unless respond_to? :tap
27
+
28
+ end
29
+
30
+ #:nodoc:
31
+ module Enumerable
32
+
33
+ #:nodoc:
34
+ def each_with_object(memo)
35
+ each { |element| yield(element, memo) }
36
+ memo
37
+ end unless [].respond_to?(:each_with_object)
38
+
39
+ end
40
+
41
+ #:nodoc:
42
+ class Hash
43
+
44
+ #:nodoc:
45
+ def assert_valid_keys(*valid_keys)
46
+ unknown_keys = keys - [valid_keys].flatten
47
+ raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
48
+ end
49
+
50
+ end
51
+
52
+ #:nodoc:
53
+ class String
54
+
55
+ #:nodoc:
56
+ def to_bson_code
57
+ BSON::Code.new(self)
58
+ end
59
+
60
+ end
@@ -0,0 +1,177 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+
18
+ module Mongo
19
+ class Pool
20
+
21
+ attr_accessor :host, :port, :size, :timeout, :safe, :checked_out
22
+
23
+ # Create a new pool of connections.
24
+ #
25
+ def initialize(connection, host, port, opts={})
26
+ @connection = connection
27
+
28
+ @host, @port = host, port
29
+
30
+ # Pool size and timeout.
31
+ @size = opts[:size] || 1
32
+ @timeout = opts[:timeout] || 5.0
33
+
34
+ # Mutex for synchronizing pool access
35
+ @connection_mutex = Mutex.new
36
+
37
+ # Condition variable for signal and wait
38
+ @queue = ConditionVariable.new
39
+
40
+ # Operations to perform on a socket
41
+ @socket_ops = Hash.new { |h, k| h[k] = [] }
42
+
43
+ @sockets = []
44
+ @pids = {}
45
+ @checked_out = []
46
+ end
47
+
48
+ def close
49
+ @sockets.each do |sock|
50
+ begin
51
+ sock.close
52
+ rescue IOError => ex
53
+ warn "IOError when attempting to close socket connected to #{@host}:#{@port}: #{ex.inspect}"
54
+ end
55
+ end
56
+ @host = @port = nil
57
+ @sockets.clear
58
+ @pids.clear
59
+ @checked_out.clear
60
+ end
61
+
62
+ # Return a socket to the pool.
63
+ def checkin(socket)
64
+ @connection_mutex.synchronize do
65
+ @checked_out.delete(socket)
66
+ @queue.signal
67
+ end
68
+ true
69
+ end
70
+
71
+ # Adds a new socket to the pool and checks it out.
72
+ #
73
+ # This method is called exclusively from #checkout;
74
+ # therefore, it runs within a mutex.
75
+ def checkout_new_socket
76
+ begin
77
+ socket = TCPSocket.new(@host, @port)
78
+ socket.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
79
+ rescue => ex
80
+ raise ConnectionFailure, "Failed to connect to host #{@host} and port #{@port}: #{ex}"
81
+ end
82
+
83
+ # If any saved authentications exist, we want to apply those
84
+ # when creating new sockets.
85
+ @connection.apply_saved_authentication(:socket => socket)
86
+
87
+ @sockets << socket
88
+ @pids[socket] = Process.pid
89
+ @checked_out << socket
90
+ socket
91
+ end
92
+
93
+ # If a user calls DB#authenticate, and several sockets exist,
94
+ # then we need a way to apply the authentication on each socket.
95
+ # So we store the apply_authentication method, and this will be
96
+ # applied right before the next use of each socket.
97
+ def authenticate_existing
98
+ @connection_mutex.synchronize do
99
+ @sockets.each do |socket|
100
+ @socket_ops[socket] << Proc.new do
101
+ @connection.apply_saved_authentication(:socket => socket)
102
+ end
103
+ end
104
+ end
105
+ end
106
+
107
+ # Store the logout op for each existing socket to be applied before
108
+ # the next use of each socket.
109
+ def logout_existing(db)
110
+ @connection_mutex.synchronize do
111
+ @sockets.each do |socket|
112
+ @socket_ops[socket] << Proc.new do
113
+ @connection.db(db).issue_logout(:socket => socket)
114
+ end
115
+ end
116
+ end
117
+ end
118
+
119
+ # Checks out the first available socket from the pool.
120
+ #
121
+ # If the pid has changed, remove the socket and check out
122
+ # new one.
123
+ #
124
+ # This method is called exclusively from #checkout;
125
+ # therefore, it runs within a mutex.
126
+ def checkout_existing_socket
127
+ socket = (@sockets - @checked_out).first
128
+ if @pids[socket] != Process.pid
129
+ @pids[socket] = nil
130
+ @sockets.delete(socket)
131
+ socket.close
132
+ checkout_new_socket
133
+ else
134
+ @checked_out << socket
135
+ socket
136
+ end
137
+ end
138
+
139
+ # Check out an existing socket or create a new socket if the maximum
140
+ # pool size has not been exceeded. Otherwise, wait for the next
141
+ # available socket.
142
+ def checkout
143
+ @connection.connect if !@connection.connected?
144
+ start_time = Time.now
145
+ loop do
146
+ if (Time.now - start_time) > @timeout
147
+ raise ConnectionTimeoutError, "could not obtain connection within " +
148
+ "#{@timeout} seconds. The max pool size is currently #{@size}; " +
149
+ "consider increasing the pool size or timeout."
150
+ end
151
+
152
+ @connection_mutex.synchronize do
153
+ socket = if @checked_out.size < @sockets.size
154
+ checkout_existing_socket
155
+ elsif @sockets.size < @size
156
+ checkout_new_socket
157
+ end
158
+
159
+ if socket
160
+
161
+ # This calls all procs, in order, scoped to existing sockets.
162
+ # At the moment, we use this to lazily authenticate and
163
+ # logout existing socket connections.
164
+ @socket_ops[socket].reject! do |op|
165
+ op.call
166
+ end
167
+
168
+ return socket
169
+ else
170
+ # Otherwise, wait
171
+ @queue.wait(@connection_mutex)
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,71 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+ module Mongo
19
+ # Simple class for comparing server versions.
20
+ class ServerVersion
21
+ include Comparable
22
+
23
+ def initialize(version)
24
+ @version = version
25
+ end
26
+
27
+ # Implements comparable.
28
+ def <=>(new)
29
+ local, new = self.to_a, to_array(new)
30
+ for n in 0...local.size do
31
+ break if elements_include_mods?(local[n], new[n])
32
+ if local[n] < new[n].to_i
33
+ result = -1
34
+ break;
35
+ elsif local[n] > new[n].to_i
36
+ result = 1
37
+ break;
38
+ end
39
+ end
40
+ result || 0
41
+ end
42
+
43
+ # Return an array representation of this server version.
44
+ def to_a
45
+ to_array(@version)
46
+ end
47
+
48
+ # Return a string representation of this server version.
49
+ def to_s
50
+ @version
51
+ end
52
+
53
+ private
54
+
55
+ # Returns true if any elements include mod symbols (-, +)
56
+ def elements_include_mods?(*elements)
57
+ elements.any? { |n| n =~ /[\-\+]/ }
58
+ end
59
+
60
+ # Converts argument to an array of integers,
61
+ # appending any mods as the final element.
62
+ def to_array(version)
63
+ array = version.split(".").map {|n| (n =~ /^\d+$/) ? n.to_i : n }
64
+ if array.last =~ /(\d+)([\-\+])/
65
+ array[array.length-1] = $1.to_i
66
+ array << $2
67
+ end
68
+ array
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,82 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+
19
+ require 'digest/md5'
20
+
21
+ module Mongo
22
+ module Support
23
+ include Mongo::Conversions
24
+ extend self
25
+
26
+ # Generate an MD5 for authentication.
27
+ #
28
+ # @param [String] username
29
+ # @param [String] password
30
+ # @param [String] nonce
31
+ #
32
+ # @return [String] a key for db authentication.
33
+ def auth_key(username, password, nonce)
34
+ Digest::MD5.hexdigest("#{nonce}#{username}#{hash_password(username, password)}")
35
+ end
36
+
37
+ # Return a hashed password for auth.
38
+ #
39
+ # @param [String] username
40
+ # @param [String] plaintext
41
+ #
42
+ # @return [String]
43
+ def hash_password(username, plaintext)
44
+ Digest::MD5.hexdigest("#{username}:mongo:#{plaintext}")
45
+ end
46
+
47
+
48
+ def validate_db_name(db_name)
49
+ unless [String, Symbol].include?(db_name.class)
50
+ raise TypeError, "db_name must be a string or symbol"
51
+ end
52
+
53
+ [" ", ".", "$", "/", "\\"].each do |invalid_char|
54
+ if db_name.include? invalid_char
55
+ raise Mongo::InvalidNSName, "database names cannot contain the character '#{invalid_char}'"
56
+ end
57
+ end
58
+ raise Mongo::InvalidNSName, "database name cannot be the empty string" if db_name.empty?
59
+ db_name
60
+ end
61
+
62
+ def format_order_clause(order)
63
+ case order
64
+ when String, Symbol then string_as_sort_parameters(order)
65
+ when Array then array_as_sort_parameters(order)
66
+ else
67
+ raise InvalidSortValueError, "Illegal sort clause, '#{order.class.name}'; must be of the form " +
68
+ "[['field1', '(ascending|descending)'], ['field2', '(ascending|descending)']]"
69
+ end
70
+ end
71
+
72
+ # Determine if a database command has succeeded by
73
+ # checking the document response.
74
+ #
75
+ # @param [Hash] doc
76
+ #
77
+ # @return [Boolean] true if the 'ok' key is either 1 or *true*.
78
+ def ok?(doc)
79
+ doc['ok'] == 1.0 || doc['ok'] == true
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,185 @@
1
+ # encoding: UTF-8
2
+
3
+ # --
4
+ # Copyright (C) 2008-2011 10gen Inc.
5
+ #
6
+ # Licensed under the Apache License, Version 2.0 (the "License");
7
+ # you may not use this file except in compliance with the License.
8
+ # You may obtain a copy of the License at
9
+ #
10
+ # http://www.apache.org/licenses/LICENSE-2.0
11
+ #
12
+ # Unless required by applicable law or agreed to in writing, software
13
+ # distributed under the License is distributed on an "AS IS" BASIS,
14
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ # See the License for the specific language governing permissions and
16
+ # limitations under the License.
17
+ # ++
18
+
19
+ module Mongo
20
+ class URIParser
21
+
22
+ DEFAULT_PORT = 27017
23
+ MONGODB_URI_MATCHER = /(([-.\w]+):([^@]+)@)?([-.\w]+)(:([\w]+))?(\/([-\w]+))?/
24
+ MONGODB_URI_SPEC = "mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/database]"
25
+ SPEC_ATTRS = [:nodes, :auths]
26
+ OPT_ATTRS = [:connect, :replicaset, :slaveok, :safe, :w, :wtimeout, :fsync]
27
+
28
+ OPT_VALID = {:connect => lambda {|arg| ['direct', 'replicaset'].include?(arg)},
29
+ :replicaset => lambda {|arg| arg.length > 0},
30
+ :slaveok => lambda {|arg| ['true', 'false'].include?(arg)},
31
+ :safe => lambda {|arg| ['true', 'false'].include?(arg)},
32
+ :w => lambda {|arg| arg =~ /^\d+$/ },
33
+ :wtimeout => lambda {|arg| arg =~ /^\d+$/ },
34
+ :fsync => lambda {|arg| ['true', 'false'].include?(arg)}
35
+ }
36
+
37
+ OPT_ERR = {:connect => "must be 'direct' or 'replicaset'",
38
+ :replicaset => "must be a string containing the name of the replica set to connect to",
39
+ :slaveok => "must be 'true' or 'false'",
40
+ :safe => "must be 'true' or 'false'",
41
+ :w => "must be an integer specifying number of nodes to replica to",
42
+ :wtimeout => "must be an integer specifying milliseconds",
43
+ :fsync => "must be 'true' or 'false'"
44
+ }
45
+
46
+ OPT_CONV = {:connect => lambda {|arg| arg},
47
+ :replicaset => lambda {|arg| arg},
48
+ :slaveok => lambda {|arg| arg == 'true' ? true : false},
49
+ :safe => lambda {|arg| arg == 'true' ? true : false},
50
+ :w => lambda {|arg| arg.to_i},
51
+ :wtimeout => lambda {|arg| arg.to_i},
52
+ :fsync => lambda {|arg| arg == 'true' ? true : false}
53
+ }
54
+
55
+ attr_reader :nodes, :auths, :connect, :replicaset, :slaveok, :safe, :w, :wtimeout, :fsync
56
+
57
+ # Parse a MongoDB URI. This method is used by Connection.from_uri.
58
+ # Returns an array of nodes and an array of db authorizations, if applicable.
59
+ #
60
+ # Note: passwords can contain any character except for a ','.
61
+ #
62
+ # @core connections
63
+ def initialize(string)
64
+ if string =~ /^mongodb:\/\//
65
+ string = string[10..-1]
66
+ else
67
+ raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
68
+ end
69
+
70
+ hosts, opts = string.split('?')
71
+ parse_hosts(hosts)
72
+ parse_options(opts)
73
+ configure_connect
74
+ end
75
+
76
+ def connection_options
77
+ opts = {}
78
+
79
+ if (@w || @wtimeout || @fsync) && !@safe
80
+ raise MongoArgumentError, "Safe must be true if w, wtimeout, or fsync is specified"
81
+ end
82
+
83
+ if @safe
84
+ if @w || @wtimeout || @fsync
85
+ safe_opts = {}
86
+ safe_opts[:w] = @w if @w
87
+ safe_opts[:wtimeout] = @wtimeout if @wtimeout
88
+ safe_opts[:fsync] = @fsync if @fsync
89
+ else
90
+ safe_opts = true
91
+ end
92
+
93
+ opts[:safe] = safe_opts
94
+ end
95
+
96
+ if @slaveok
97
+ if @connect == 'direct'
98
+ opts[:slave_ok] = true
99
+ else
100
+ opts[:read_secondary] = true
101
+ end
102
+ end
103
+
104
+ opts[:rs_name] = @replicaset if @replicaset
105
+
106
+ opts
107
+ end
108
+
109
+ private
110
+
111
+ def parse_hosts(hosts)
112
+ @nodes = []
113
+ @auths = []
114
+ specs = hosts.split(',')
115
+ specs.each do |spec|
116
+ matches = MONGODB_URI_MATCHER.match(spec)
117
+ if !matches
118
+ raise MongoArgumentError, "MongoDB URI must match this spec: #{MONGODB_URI_SPEC}"
119
+ end
120
+
121
+ uname = matches[2]
122
+ pwd = matches[3]
123
+ host = matches[4]
124
+ port = matches[6] || DEFAULT_PORT
125
+ if !(port.to_s =~ /^\d+$/)
126
+ raise MongoArgumentError, "Invalid port #{port}; port must be specified as digits."
127
+ end
128
+ port = port.to_i
129
+ db = matches[8]
130
+
131
+ if uname && pwd && db
132
+ auths << {'db_name' => db, 'username' => uname, 'password' => pwd}
133
+ elsif uname || pwd || db
134
+ raise MongoArgumentError, "MongoDB URI must include all three of username, password, " +
135
+ "and db if any one of these is specified."
136
+ end
137
+
138
+ @nodes << [host, port]
139
+ end
140
+ end
141
+
142
+ # This method uses the lambdas defined in OPT_VALID and OPT_CONV to validate
143
+ # and convert the given options.
144
+ def parse_options(opts)
145
+ # initialize instance variables for available options
146
+ OPT_VALID.keys.each { |k| instance_variable_set("@#{k}", nil) }
147
+
148
+ return unless opts
149
+
150
+ separator = opts.include?('&') ? '&' : ';'
151
+ opts.split(separator).each do |attr|
152
+ key, value = attr.split('=')
153
+ key = key.to_sym
154
+ value = value.strip.downcase
155
+ if !OPT_ATTRS.include?(key)
156
+ raise MongoArgumentError, "Invalid Mongo URI option #{key}"
157
+ end
158
+
159
+ if OPT_VALID[key].call(value)
160
+ instance_variable_set("@#{key}", OPT_CONV[key].call(value))
161
+ else
162
+ raise MongoArgumentError, "Invalid value for #{key}: #{OPT_ERR[key]}"
163
+ end
164
+ end
165
+ end
166
+
167
+ def configure_connect
168
+ if @nodes.length > 1 && !@connect
169
+ @connect = 'replicaset'
170
+ end
171
+
172
+ if !@connect
173
+ if @nodes.length > 1
174
+ @connect = 'replicaset'
175
+ else
176
+ @connect = 'direct'
177
+ end
178
+ end
179
+
180
+ if @connect == 'direct' && @replicaset
181
+ raise MongoArgumentError, "If specifying a replica set name, please also specify that connect=replicaset"
182
+ end
183
+ end
184
+ end
185
+ end