moped 1.0.0.beta → 1.0.0.rc

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of moped might be problematic. Click here for more details.

data/lib/moped/errors.rb CHANGED
@@ -8,35 +8,70 @@ module Moped
8
8
  # Generic error class for exceptions related to connection failures.
9
9
  class ConnectionFailure < StandardError; end
10
10
 
11
- # Tag applied to unhandled exceptions on a node.
12
- module SocketError end
11
+ # Raised when providing an invalid string from an object id.
12
+ class InvalidObjectId < StandardError
13
+
14
+ # Create the new error.
15
+ #
16
+ # @example Create the new error.
17
+ # InvalidObjectId.new("test")
18
+ #
19
+ # @param [ String ] string The provided id.
20
+ #
21
+ # @since 1.0.0
22
+ def initialize(string)
23
+ super("'#{string}' is not a valid object id.")
24
+ end
25
+ end
13
26
 
14
27
  # Generic error class for exceptions generated on the remote MongoDB
15
28
  # server.
16
29
  class MongoError < StandardError
17
- # @return the command that generated the error
18
- attr_reader :command
19
30
 
20
- # @return [Hash] the details about the error
21
- attr_reader :details
31
+ # @attribute [r] details The details about the error.
32
+ # @attribute [r] command The command that generated the error.
33
+ attr_reader :details, :command
22
34
 
23
35
  # Create a new operation failure exception.
24
36
  #
25
- # @param command the command that generated the error
26
- # @param [Hash] details the details about the error
37
+ # @example Create the new error.
38
+ # MongoError.new(command, details)
39
+ #
40
+ # @param [ Object ] command The command that generated the error.
41
+ # @param [ Hash ] details The details about the error.
42
+ #
43
+ # @since 1.0.0
27
44
  def initialize(command, details)
28
- @command = command
29
- @details = details
30
-
31
- super build_message
45
+ @command, @details = command, details
46
+ super(build_message)
32
47
  end
33
48
 
34
49
  private
35
50
 
51
+ # Build the error message.
52
+ #
53
+ # @api private
54
+ #
55
+ # @example Build the message.
56
+ # error.build_message
57
+ #
58
+ # @return [ String ] The message.
59
+ #
60
+ # @since 1.0.0
36
61
  def build_message
37
62
  "The operation: #{command.inspect}\n#{error_message}"
38
63
  end
39
64
 
65
+ # Get the error message.
66
+ #
67
+ # @api private
68
+ #
69
+ # @example Get the error message.
70
+ # error.error_message
71
+ #
72
+ # @return [ String ] The message.
73
+ #
74
+ # @since 1.0.0
40
75
  def error_message
41
76
  err = details["err"] || details["errmsg"] || details["$err"]
42
77
 
@@ -49,6 +84,9 @@ module Moped
49
84
  end
50
85
  end
51
86
 
87
+ # Exception raised when authentication fails.
88
+ class AuthenticationFailure < MongoError; end
89
+
52
90
  # Exception class for exceptions generated as a direct result of an
53
91
  # operation, such as a failed insert or an invalid command.
54
92
  class OperationFailure < MongoError; end
@@ -56,21 +94,13 @@ module Moped
56
94
  # Exception raised on invalid queries.
57
95
  class QueryFailure < MongoError; end
58
96
 
59
- # Exception raised when authentication fails.
60
- class AuthenticationFailure < MongoError; end
61
-
62
- # Raised when providing an invalid string from an object id.
63
- class InvalidObjectId < StandardError
64
- def initialize(string)
65
- super("'#{string}' is not a valid object id.")
66
- end
67
- end
68
-
69
97
  # @api private
70
98
  #
71
99
  # Internal exception raised by Node#ensure_primary and captured by
72
100
  # Cluster#with_primary.
73
101
  class ReplicaSetReconfigured < StandardError; end
74
102
 
103
+ # Tag applied to unhandled exceptions on a node.
104
+ module SocketError; end
75
105
  end
76
106
  end
data/lib/moped/indexes.rb CHANGED
@@ -1,39 +1,31 @@
1
1
  module Moped
2
+
3
+ # Defines behaviour around indexes.
2
4
  class Indexes
3
5
  include Enumerable
4
6
 
5
- private
6
-
7
- attr_reader :database
8
- attr_reader :collection_name
9
- attr_reader :namespace
10
-
11
- def initialize(database, collection_name)
12
- @database = database
13
- @collection_name = collection_name
14
- @namespace = "#{database.name}.#{collection_name}"
15
- end
16
-
17
- public
7
+ # @attribute [r] collection_name The collection name.
8
+ # @attribute [r] database The database.
9
+ # @attribute [r] namespace The index namespace.
10
+ attr_reader :collection_name, :database, :namespace
18
11
 
19
12
  # Retrive an index by its definition.
20
13
  #
21
- # @param [Hash] key an index key definition
22
- # @return [Hash, nil] the index with the provided key, or nil.
23
- #
24
- # @example
14
+ # @example Get the index.
25
15
  # session[:users].indexes[id: 1]
26
16
  # # => {"v"=>1, "key"=>{"_id"=>1}, "ns"=>"moped_test.users", "name"=>"_id_" }
17
+ #
18
+ # @param [ Hash ] key The index definition.
19
+ #
20
+ # @return [ Hash, nil ] The index with the provided key, or nil.
21
+ #
22
+ # @since 1.0.0
27
23
  def [](key)
28
24
  database[:"system.indexes"].find(ns: namespace, key: key).one
29
25
  end
30
26
 
31
27
  # Create an index unless it already exists.
32
28
  #
33
- # @param [Hash] key the index spec
34
- # @param [Hash] options the options for the index.
35
- # @see http://www.mongodb.org/display/DOCS/Indexes#Indexes-CreationOptions
36
- #
37
29
  # @example Without options
38
30
  # session[:users].indexes.create(name: 1)
39
31
  # session[:users].indexes[name: 1]
@@ -51,18 +43,23 @@ module Moped
51
43
  # "ns"=>"moped_test.users",
52
44
  # "dropDups"=>true,
53
45
  # "name"=>"location_2d_name_1"}
46
+ #
47
+ # @param [ Hash ] key The index spec.
48
+ # @param [ Hash ] options The options for the index.
49
+ #
50
+ # @return [ Hash ] The index spec.
51
+ #
52
+ # @see http://www.mongodb.org/display/DOCS/Indexes#Indexes-CreationOptions
53
+ #
54
+ # @since 1.0.0
54
55
  def create(key, options = {})
55
56
  spec = options.merge(ns: namespace, key: key)
56
57
  spec[:name] ||= key.to_a.join("_")
57
-
58
58
  database[:"system.indexes"].insert(spec)
59
59
  end
60
60
 
61
61
  # Drop an index, or all indexes.
62
62
  #
63
- # @param [Hash] key the index's key
64
- # @return [Boolean] whether the indexes were dropped.
65
- #
66
63
  # @example Drop all indexes
67
64
  # session[:users].indexes.count # => 3
68
65
  # # Does not drop the _id index
@@ -72,6 +69,12 @@ module Moped
72
69
  # @example Drop a particular index
73
70
  # session[:users].indexes.drop(name: 1)
74
71
  # session[:users].indexes[name: 1] # => nil
72
+ #
73
+ # @param [ Hash ] key The index's key.
74
+ #
75
+ # @return [ Boolean ] Whether the indexes were dropped.
76
+ #
77
+ # @since 1.0.0
75
78
  def drop(key = nil)
76
79
  if key
77
80
  index = self[key] or return false
@@ -79,15 +82,38 @@ module Moped
79
82
  else
80
83
  name = "*"
81
84
  end
82
-
83
85
  result = database.command deleteIndexes: collection_name, index: name
84
86
  result["ok"] == 1
85
87
  end
86
88
 
87
- # @yield [Hash] each index for the collection.
89
+ # Iterate over each of the indexes for the collection.
90
+ #
91
+ # @example Iterate over the indexes.
92
+ # indexes.each do |spec|
93
+ # #...
94
+ # end
95
+ #
96
+ # @return [ Enumerator ] The enumerator.
97
+ #
98
+ # @since 1.0.0
99
+ #
100
+ # @yield [ Hash ] Each index for the collection.
88
101
  def each(&block)
89
102
  database[:"system.indexes"].find(ns: namespace).each(&block)
90
103
  end
91
104
 
105
+ # Initialize the indexes.
106
+ #
107
+ # @example Create the new indexes.
108
+ # Indexes.new(database, :artists)
109
+ #
110
+ # @param [ Database ] database The database.
111
+ # @param [ String, Symbol ] collection_name The name of the collection.
112
+ #
113
+ # @since 1.0.0
114
+ def initialize(database, collection_name)
115
+ @database, @collection_name = database, collection_name
116
+ @namespace = "#{database.name}.#{collection_name}"
117
+ end
92
118
  end
93
119
  end
data/lib/moped/logging.rb CHANGED
@@ -1,25 +1,58 @@
1
1
  module Moped
2
+
3
+ # Contains behaviour for logging.
2
4
  module Logging
5
+
6
+ # Get the logger.
7
+ #
8
+ # @example Get the logger.
9
+ # Logging.logger
10
+ #
11
+ # @return [ Logger ] The logger.
12
+ #
13
+ # @since 1.0.0
3
14
  def logger
4
15
  return @logger if defined?(@logger)
5
-
6
16
  @logger = rails_logger || default_logger
7
17
  end
8
18
 
19
+ # Get the rails logger.
20
+ #
21
+ # @example Get the rails logger.
22
+ # Logging.rails_logger
23
+ #
24
+ # @return [ Logger ] The Rails logger.
25
+ #
26
+ # @since 1.0.0
9
27
  def rails_logger
10
28
  defined?(Rails) && Rails.respond_to?(:logger) && Rails.logger
11
29
  end
12
30
 
31
+ # Get the default logger.
32
+ #
33
+ # @example Get the default logger.
34
+ # Logging.default_logger
35
+ #
36
+ # @return [ Logger ] The default logger.
37
+ #
38
+ # @since 1.0.0
13
39
  def default_logger
14
40
  Logger.new(STDOUT).tap do |logger|
15
41
  logger.level = Logger::INFO
16
42
  end
17
43
  end
18
44
 
45
+ # Set the logger.
46
+ #
47
+ # @example Set the logger.
48
+ # Logging.logger = logger
49
+ #
50
+ # @return [ Logger ] The logger.
51
+ #
52
+ # @since 1.0.0
19
53
  def logger=(logger)
20
54
  @logger = logger
21
55
  end
22
56
  end
23
-
24
57
  extend Logging
25
58
  end
data/lib/moped/node.rb CHANGED
@@ -1,139 +1,111 @@
1
1
  module Moped
2
- class Node
3
-
4
- attr_reader :address
5
- attr_reader :resolved_address
6
- attr_reader :ip_address
7
- attr_reader :port
8
2
 
9
- attr_reader :peers
10
- attr_reader :timeout
3
+ # Represents a client to a node in a server cluster.
4
+ #
5
+ # @api private
6
+ class Node
11
7
 
12
- def initialize(address)
13
- @address = address
8
+ # @attribute [r] address The address of the node.
9
+ # @attribute [r] down_at The time the server node went down.
10
+ # @attribute [r] ip_address The node's ip.
11
+ # @attribute [r] peers Other peers in the replica set.
12
+ # @attribute [r] port The connection port.
13
+ # @attribute [r] resolved_address The host/port pair.
14
+ # @attribute [r] timeout The connection timeout.
15
+ attr_reader :address, :down_at, :ip_address, :peers, :port, :resolved_address, :timeout
14
16
 
15
- host, port = address.split(":")
16
- @ip_address = ::Socket.getaddrinfo(host, nil, ::Socket::AF_INET, ::Socket::SOCK_STREAM).first[3]
17
- @port = port.to_i
18
- @resolved_address = "#{@ip_address}:#{@port}"
17
+ # Is this node equal to another?
18
+ #
19
+ # @example Is the node equal to another.
20
+ # node == other
21
+ #
22
+ # @param [ Node ] other The other node.
23
+ #
24
+ # @return [ true, false ] If the addresses are equal.
25
+ #
26
+ # @since 1.0.0
27
+ def ==(other)
28
+ resolved_address == other.resolved_address
29
+ end
30
+ alias :eql? :==
19
31
 
20
- @timeout = 5
32
+ # Apply the authentication details to this node.
33
+ #
34
+ # @example Apply authentication.
35
+ # node.apply_auth([ :db, "user", "pass" ])
36
+ #
37
+ # @param [ Array<String> ] credentials The db, username, and password.
38
+ #
39
+ # @return [ Node ] The authenticated node.
40
+ #
41
+ # @since 1.0.0
42
+ def apply_auth(credentials)
43
+ unless auth == credentials
44
+ logouts = auth.keys - credentials.keys
45
+ logouts.each { |database| logout(database) }
46
+ credentials.each do |database, (username, password)|
47
+ login(database, username, password) unless auth[database] == [username, password]
48
+ end
49
+ end
50
+ self
21
51
  end
22
52
 
53
+ # Execute a command against a database.
54
+ #
55
+ # @example Execute a command.
56
+ # node.command(database, { ping: 1 })
57
+ #
58
+ # @param [ Database ] database The database to run the command on.
59
+ # @param [ Hash ] cmd The command to execute.
60
+ # @options [ Hash ] options The command options.
61
+ #
62
+ # @raise [ OperationFailure ] If the command failed.
63
+ #
64
+ # @return [ Hash ] The result of the command.
65
+ #
66
+ # @since 1.0.0
23
67
  def command(database, cmd, options = {})
24
68
  operation = Protocol::Command.new(database, cmd, options)
25
69
 
26
70
  process(operation) do |reply|
27
71
  result = reply.documents[0]
28
-
29
72
  raise Errors::OperationFailure.new(
30
73
  operation, result
31
74
  ) if result["ok"] != 1 || result["err"] || result["errmsg"]
32
-
33
75
  result
34
76
  end
35
77
  end
36
78
 
37
- def kill_cursors(cursor_ids)
38
- process Protocol::KillCursors.new(cursor_ids)
39
- end
40
-
41
- def get_more(database, collection, cursor_id, limit)
42
- process Protocol::GetMore.new(database, collection, cursor_id, limit)
43
- end
44
-
45
- def remove(database, collection, selector, options = {})
46
- process Protocol::Delete.new(database, collection, selector, options)
47
- end
48
-
49
- def update(database, collection, selector, change, options = {})
50
- process Protocol::Update.new(database, collection, selector, change, options)
51
- end
52
-
53
- def insert(database, collection, documents)
54
- process Protocol::Insert.new(database, collection, documents)
55
- end
56
-
57
- def query(database, collection, selector, options = {})
58
- operation = Protocol::Query.new(database, collection, selector, options)
59
-
60
- process operation do |reply|
61
- if reply.flags.include? :query_failure
62
- raise Errors::QueryFailure.new(operation, reply.documents.first)
63
- end
64
-
65
- reply
66
- end
67
- end
68
-
69
- # @return [true/false] whether the node needs to be refreshed.
70
- def needs_refresh?(time)
71
- !@refreshed_at || @refreshed_at < time
72
- end
73
-
74
- def primary?
75
- @primary
76
- end
77
-
78
- def secondary?
79
- @secondary
80
- end
81
-
82
- # Refresh information about the node, such as it's status in the replica
83
- # set and it's known peers.
79
+ # Is the node down?
84
80
  #
85
- # Returns nothing.
86
- # Raises Errors::ConnectionFailure if the node cannot be reached
87
- # Raises Errors::ReplicaSetReconfigured if the node is no longer a primary node and
88
- # refresh was called within an +#ensure_primary+ block.
89
- def refresh
90
- info = command "admin", ismaster: 1
91
-
92
- @refreshed_at = Time.now
93
- primary = true if info["ismaster"]
94
- secondary = true if info["secondary"]
95
-
96
- peers = []
97
- peers.push info["primary"] if info["primary"]
98
- peers.concat info["hosts"] if info["hosts"]
99
- peers.concat info["passives"] if info["passives"]
100
- peers.concat info["arbiters"] if info["arbiters"]
101
-
102
- @peers = peers.map { |peer| Node.new(peer) }
103
- @primary, @secondary = primary, secondary
104
-
105
- if !primary && Threaded.executing?(:ensure_primary)
106
- raise Errors::ReplicaSetReconfigured, "#{inspect} is no longer the primary node."
107
- end
108
- end
109
-
110
- attr_reader :down_at
111
-
81
+ # @example Is the node down?
82
+ # node.down?
83
+ #
84
+ # @return [ Time, nil ] The time the node went down, or nil if up.
85
+ #
86
+ # @since 1.0.0
112
87
  def down?
113
88
  @down_at
114
89
  end
115
90
 
116
- # Set a flag on the node for the duration of provided block so that an
117
- # exception is raised if the node is no longer the primary node.
118
- #
119
- # Returns nothing.
120
- def ensure_primary
121
- Threaded.begin :ensure_primary
122
- yield
123
- ensure
124
- Threaded.end :ensure_primary
125
- end
126
-
127
91
  # Yields the block if a connection can be established, retrying when a
128
92
  # connection error is raised.
129
93
  #
130
- # @raises ConnectionFailure when a connection cannot be established.
94
+ # @example Ensure we are connection.
95
+ # node.ensure_connected do
96
+ # #...
97
+ # end
98
+ #
99
+ # @raises [ ConnectionFailure ] When a connection cannot be established.
100
+ #
101
+ # @return [ nil ] nil.
102
+ #
103
+ # @since 1.0.0
131
104
  def ensure_connected
132
105
  # Don't run the reconnection login if we're already inside an
133
106
  # +ensure_connected+ block.
134
- return yield if Threaded.executing? :connection
135
- Threaded.begin :connection
136
-
107
+ return yield if Threaded.executing?(:connection)
108
+ Threaded.begin(:connection)
137
109
  retry_on_failure = true
138
110
 
139
111
  begin
@@ -167,44 +139,261 @@ module Moped
167
139
  raise $!.extend(Errors::SocketError)
168
140
  end
169
141
  ensure
170
- Threaded.end :connection
142
+ Threaded.end(:connection)
171
143
  end
172
144
 
173
- def pipeline
174
- Threaded.begin :pipeline
145
+ # Set a flag on the node for the duration of provided block so that an
146
+ # exception is raised if the node is no longer the primary node.
147
+ #
148
+ # @example Ensure this node is primary.
149
+ # node.ensure_primary do
150
+ # #...
151
+ # end
152
+ #
153
+ # @return [ nil ] nil.
154
+ #
155
+ # @since 1.0.0
156
+ def ensure_primary
157
+ Threaded.begin(:ensure_primary)
158
+ yield
159
+ ensure
160
+ Threaded.end(:ensure_primary)
161
+ end
175
162
 
163
+ # Execute a get more operation on the node.
164
+ #
165
+ # @example Execute a get more.
166
+ # node.get_more(database, collection, 12345, -1)
167
+ #
168
+ # @param [ Database ] database The database to get more from.
169
+ # @param [ Collection ] collection The collection to get more from.
170
+ # @param [ Integer ] cursor_id The id of the cursor on the server.
171
+ # @param [ Integer ] limit The number of documents to limit.
172
+ #
173
+ # @return [ Message ] The result of the operation.
174
+ #
175
+ # @since 1.0.0
176
+ def get_more(database, collection, cursor_id, limit)
177
+ process(Protocol::GetMore.new(database, collection, cursor_id, limit))
178
+ end
179
+
180
+ # Get the hash identifier for the node.
181
+ #
182
+ # @example Get the hash identifier.
183
+ # node.hash
184
+ #
185
+ # @return [ Integer ] The hash identifier.
186
+ #
187
+ # @since 1.0.0
188
+ def hash
189
+ [ ip_address, port ].hash
190
+ end
191
+
192
+ # Creat the new node.
193
+ #
194
+ # @example Create the new node.
195
+ # Node.new("127.0.0.1:27017")
196
+ #
197
+ # @param [ String ] address The location of the server node.
198
+ #
199
+ # @since 1.0.0
200
+ def initialize(address)
201
+ @address = address
202
+
203
+ host, port = address.split(":")
204
+ @ip_address = ::Socket.getaddrinfo(host, nil, ::Socket::AF_INET, ::Socket::SOCK_STREAM).first[3]
205
+ @port = port.to_i
206
+ @resolved_address = "#{@ip_address}:#{@port}"
207
+
208
+ @timeout = 5
209
+ @down_at = nil
210
+ @refreshed_at = nil
211
+ @primary = nil
212
+ @secondary = nil
213
+ end
214
+
215
+ # Insert documents into the database.
216
+ #
217
+ # @example Insert documents.
218
+ # node.insert(database, collection, [{ name: "Tool" }])
219
+ #
220
+ # @param [ Database ] database The database to insert to.
221
+ # @param [ Collection ] collection The collection to insert to.
222
+ # @param [ Array<Hash> ] documents The documents to insert.
223
+ #
224
+ # @return [ Message ] The result of the operation.
225
+ #
226
+ # @since 1.0.0
227
+ def insert(database, collection, documents)
228
+ process(Protocol::Insert.new(database, collection, documents))
229
+ end
230
+
231
+ # Kill all provided cursors on the node.
232
+ #
233
+ # @example Kill all the provided cursors.
234
+ # node.kill_cursors([ 12345 ])
235
+ #
236
+ # @param [ Array<Integer> ] cursor_ids The cursor ids.
237
+ #
238
+ # @return [ Message ] The result of the operation.
239
+ #
240
+ # @since 1.0.0
241
+ def kill_cursors(cursor_ids)
242
+ process(Protocol::KillCursors.new(cursor_ids))
243
+ end
244
+
245
+ # Does the node need to be refreshed?
246
+ #
247
+ # @example Does the node require refreshing?
248
+ # node.needs_refresh?(time)
249
+ #
250
+ # @param [ Time ] time The next referesh time.
251
+ #
252
+ # @return [ true, false] Whether the node needs to be refreshed.
253
+ #
254
+ # @since 1.0.0
255
+ def needs_refresh?(time)
256
+ !@refreshed_at || @refreshed_at < time
257
+ end
258
+
259
+ # Execute a pipeline of commands, for example a safe mode persist.
260
+ #
261
+ # @example Execute a pipeline.
262
+ # node.pipeline do
263
+ # #...
264
+ # end
265
+ #
266
+ # @return [ nil ] nil.
267
+ #
268
+ # @since 1.0.0
269
+ def pipeline
270
+ Threaded.begin(:pipeline)
176
271
  begin
177
272
  yield
178
273
  ensure
179
- Threaded.end :pipeline
274
+ Threaded.end(:pipeline)
180
275
  end
276
+ flush unless Threaded.executing?(:pipeline)
277
+ end
181
278
 
182
- flush unless Threaded.executing? :pipeline
279
+ # Is the node the replica set primary?
280
+ #
281
+ # @example Is the node the primary?
282
+ # node.primary?
283
+ #
284
+ # @return [ true, false ] If the node is the primary.
285
+ #
286
+ # @since 1.0.0
287
+ def primary?
288
+ @primary
183
289
  end
184
290
 
185
- def apply_auth(credentials)
186
- unless auth == credentials
187
- logouts = auth.keys - credentials.keys
291
+ # Execute a query on the node.
292
+ #
293
+ # @example Execute a query.
294
+ # node.query(database, collection, { name: "Tool" })
295
+ #
296
+ # @param [ Database ] database The database to query from.
297
+ # @param [ Collection ] collection The collection to query from.
298
+ # @param [ Hash ] selector The query selector.
299
+ # @param [ Hash ] options The query options.
300
+ #
301
+ # @raise [ QueryFailure ] If the query had an error.
302
+ #
303
+ # @return [ Message ] The result of the operation.
304
+ #
305
+ # @since 1.0.0
306
+ def query(database, collection, selector, options = {})
307
+ operation = Protocol::Query.new(database, collection, selector, options)
188
308
 
189
- logouts.each do |database|
190
- logout database
309
+ process operation do |reply|
310
+ if reply.flags.include?(:query_failure)
311
+ raise Errors::QueryFailure.new(operation, reply.documents.first)
191
312
  end
313
+ reply
314
+ end
315
+ end
192
316
 
193
- credentials.each do |database, (username, password)|
194
- login(database, username, password) unless auth[database] == [username, password]
195
- end
317
+ # Refresh information about the node, such as it's status in the replica
318
+ # set and it's known peers.
319
+ #
320
+ # @example Refresh the node.
321
+ # node.refresh
322
+ #
323
+ # @raise [ ConnectionFailure ] If the node cannot be reached.
324
+
325
+ # @raise [ ReplicaSetReconfigured ] If the node is no longer a primary node and
326
+ # refresh was called within an +#ensure_primary+ block.
327
+ #
328
+ # @return [ nil ] nil.
329
+ #
330
+ # @since 1.0.0
331
+ def refresh
332
+ info = command("admin", ismaster: 1)
333
+
334
+ @refreshed_at = Time.now
335
+ primary = true if info["ismaster"]
336
+ secondary = true if info["secondary"]
337
+
338
+ peers = []
339
+ peers.push(info["primary"]) if info["primary"]
340
+ peers.concat(info["hosts"]) if info["hosts"]
341
+ peers.concat(info["passives"]) if info["passives"]
342
+ peers.concat(info["arbiters"]) if info["arbiters"]
343
+
344
+ @peers = peers.map { |peer| Node.new(peer) }
345
+ @primary, @secondary = primary, secondary
346
+
347
+ if !primary && Threaded.executing?(:ensure_primary)
348
+ raise Errors::ReplicaSetReconfigured, "#{inspect} is no longer the primary node."
196
349
  end
350
+ end
197
351
 
198
- self
352
+ # Execute a remove command for the provided selector.
353
+ #
354
+ # @example Remove documents.
355
+ # node.remove(database, collection, { name: "Tool" })
356
+ #
357
+ # @param [ Database ] database The database to remove from.
358
+ # @param [ Collection ] collection The collection to remove from.
359
+ # @param [ Hash ] selector The query selector.
360
+ # @param [ Hash ] options The remove options.
361
+ #
362
+ # @return [ Message ] The result of the operation.
363
+ #
364
+ # @since 1.0.0
365
+ def remove(database, collection, selector, options = {})
366
+ process(Protocol::Delete.new(database, collection, selector, options))
199
367
  end
200
368
 
201
- def ==(other)
202
- resolved_address == other.resolved_address
369
+ # Is the node a replica set secondary?
370
+ #
371
+ # @example Is the node a secondary?
372
+ # node.secondary?
373
+ #
374
+ # @return [ true, false ] If the node is a secondary.
375
+ #
376
+ # @since 1.0.0
377
+ def secondary?
378
+ @secondary
203
379
  end
204
- alias eql? ==
205
380
 
206
- def hash
207
- [ip_address, port].hash
381
+ # Execute an update command for the provided selector.
382
+ #
383
+ # @example Update documents.
384
+ # node.update(database, collection, { name: "Tool" }, { likes: 1000 })
385
+ #
386
+ # @param [ Database ] database The database to update.
387
+ # @param [ Collection ] collection The collection to update.
388
+ # @param [ Hash ] selector The query selector.
389
+ # @param [ Hash ] change The updates.
390
+ # @param [ Hash ] options The update options.
391
+ #
392
+ # @return [ Message ] The result of the operation.
393
+ #
394
+ # @since 1.0.0
395
+ def update(database, collection, selector, change, options = {})
396
+ process(Protocol::Update.new(database, collection, selector, change, options))
208
397
  end
209
398
 
210
399
  private
@@ -276,6 +465,8 @@ module Moped
276
465
  raise Errors::ConnectionFailure, "Timed out connection to Mongo on #{address}"
277
466
  rescue Errno::ECONNREFUSED
278
467
  raise Errors::ConnectionFailure, "Could not connect to Mongo on #{address}"
468
+ rescue Errno::ECONNRESET
469
+ raise Errors::ConnectionFailure, "Connection reset to Mongo on #{address}"
279
470
  end
280
471
 
281
472
  def process(operation, &callback)
@@ -329,6 +520,5 @@ module Moped
329
520
  logger.debug indent + last.log_inspect + runtime
330
521
  end
331
522
  end
332
-
333
523
  end
334
524
  end