Skip to content

Commit 5c60606

Browse files
committed
Merge pull request #646 from estolfo/crud
CRUD spec compliance
2 parents 10019f5 + 4a45d12 commit 5c60606

26 files changed

+1903
-184
lines changed

lib/mongo/collection.rb

Lines changed: 271 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,23 @@ def ==(other)
5757
name == other.name && database == other.database && options == other.options
5858
end
5959

60+
# Instantiate a new collection.
61+
#
62+
# @example Instantiate a new collection.
63+
# Mongo::Collection.new(database, 'test')
64+
#
65+
# @param [ Mongo::Database ] database The collection's database.
66+
# @param [ String, Symbol ] name The collection name.
67+
# @param [ Hash ] options The collection options.
68+
#
69+
# @since 2.0.0
70+
def initialize(database, name, options = {})
71+
raise Error::InvalidCollectionName.new unless name
72+
@database = database
73+
@name = name.to_s.freeze
74+
@options = options.freeze
75+
end
76+
6077
# Is the collection capped?
6178
#
6279
# @example Is the collection capped?
@@ -108,12 +125,96 @@ def drop
108125
# collection.find
109126
#
110127
# @param [ Hash ] filter The filter to use in the find.
128+
# @param [ Hash ] options The options for the find.
129+
#
130+
# @option options [ true, false ] :allow_partial_results Allows the query to get partial
131+
# results if some shards are down.
132+
# @option options [ Integer ] :batch_size The number of documents returned in each batch
133+
# of results from MongoDB.
134+
# @option options [ String ] :comment Associate a comment with the query.
135+
# @option options [ :tailable, :tailable_await ] :cursor_type The type of cursor to use.
136+
# @option options [ Integer ] :limit The max number of docs to return from the query.
137+
# @option options [ Integer ] :max_time_ms The maximum amount of time to allow the query
138+
# to run in milliseconds.
139+
# @option options [ Hash ] :modifiers A document containing meta-operators modifying the
140+
# output or behavior of a query.
141+
# @option options [ true, false ] :no_cursor_timeout The server normally times out idle
142+
# cursors after an inactivity period (10 minutes) to prevent excess memory use.
143+
# Set this option to prevent that.
144+
# @option options [ true, false ] :oplog_replay Internal replication use only - driver
145+
# should not set.
146+
# @option options [ Hash ] :projection The fields to include or exclude from each doc
147+
# in the result set.
148+
# @option options [ Integer ] :skip The number of docs to skip before returning results.
149+
# @option options [ Hash ] :sort The key and direction pairs by which the result set
150+
# will be sorted.
111151
#
112152
# @return [ CollectionView ] The collection view.
113153
#
114154
# @since 2.0.0
115-
def find(filter = nil)
116-
View.new(self, filter || {})
155+
def find(filter = nil, options = {})
156+
View.new(self, filter || {}, options)
157+
end
158+
159+
# Perform an aggregation on the collection.
160+
#
161+
# @example Perform an aggregation.
162+
# collection.aggregate([ { "$group" => { "_id" => "$city", "tpop" => { "$sum" => "$pop" }}} ])
163+
#
164+
# @param [ Array<Hash> ] pipeline The aggregation pipeline.
165+
# @param [ Hash ] options The aggregation options.
166+
#
167+
# @option options [ true, false ] :allow_disk_use Set to true if disk usage is allowed during
168+
# the aggregation.
169+
# @option options [ Integer ] :batch_size The number of documents to return per batch.
170+
# @option options [ Integer ] :max_time_ms The maximum amount of time in milliseconds to allow the
171+
# aggregation to run.
172+
# @option options [ true, false ] :use_cursor Indicates whether the command will request that the server
173+
# provide results using a cursor.
174+
#
175+
# @return [ Aggregation ] The aggregation object.
176+
#
177+
# @since 2.1.0
178+
def aggregate(pipeline, options = {})
179+
View.new(self, {}).aggregate(pipeline, options)
180+
end
181+
182+
# Get a count of matching documents in the collection.
183+
#
184+
# @example Get the count.
185+
# collection.count(name: 1)
186+
#
187+
# @param [ Hash ] filter A filter for matching documents.
188+
# @param [ Hash ] options The count options.
189+
#
190+
# @option options [ Hash ] :hint The index to use.
191+
# @option options [ Integer ] :limit The maximum number of documents to count.
192+
# @option options [ Integer ] :max_time_ms The maximum amount of time to allow the command to run.
193+
# @option options [ Integer ] :skip The number of documents to skip before counting.
194+
#
195+
# @return [ Integer ] The document count.
196+
#
197+
# @since 2.1.0
198+
def count(filter = nil, options = {})
199+
View.new(self, filter || {}).count(options)
200+
end
201+
202+
# Get a list of distinct values for a specific field.
203+
#
204+
# @example Get the distinct values.
205+
# collection.distinct('name')
206+
#
207+
# @param [ Symbol, String ] field_name The name of the field.
208+
# @param [ Hash ] filter The documents from which to retrieve the distinct values.
209+
# @param [ Hash ] options The distinct command options.
210+
#
211+
# @option options [ Integer ] :max_time_ms The maximum amount of time to allow the command to run.
212+
#
213+
# @return [ Array<Object> ] The list of distinct values.
214+
#
215+
# @since 2.1.0
216+
def distinct(field_name, filter = nil, options = {})
217+
View.new(self, filter || {}).distinct(field_name, options)
117218
end
118219

119220
# Get a view of all indexes for this collection. Can be iterated or has
@@ -131,23 +232,6 @@ def indexes(options = {})
131232
Index::View.new(self, options)
132233
end
133234

134-
# Instantiate a new collection.
135-
#
136-
# @example Instantiate a new collection.
137-
# Mongo::Collection.new(database, 'test')
138-
#
139-
# @param [ Mongo::Database ] database The collection's database.
140-
# @param [ String, Symbol ] name The collection name.
141-
# @param [ Hash ] options The collection options.
142-
#
143-
# @since 2.0.0
144-
def initialize(database, name, options = {})
145-
raise Error::InvalidCollectionName.new unless name
146-
@database = database
147-
@name = name.to_s.freeze
148-
@options = options.freeze
149-
end
150-
151235
# Get a pretty printed string inspection for the collection.
152236
#
153237
# @example Inspect the collection.
@@ -205,13 +289,181 @@ def insert_many(documents, options = {})
205289
# @param [ Array<Hash> ] operations The operations.
206290
# @param [ Hash ] options The options.
207291
#
292+
# @option options [ true, false ] :ordered Whether the operations
293+
# should be executed in order.
294+
# @option options [ Hash ] :write_concern The write concern options.
295+
# Can be :w => Integer, :fsync => Boolean, :j => Boolean.
296+
#
208297
# @return [ BulkWrite::Result ] The result of the operation.
209298
#
210299
# @since 2.0.0
211300
def bulk_write(operations, options = {})
212301
BulkWrite.get(self, operations, options).execute
213302
end
214303

304+
# Remove a document from the collection.
305+
#
306+
# @example Remove a single document from the collection.
307+
# collection.delete_one
308+
#
309+
# @param [ Hash ] filter The filter to use.
310+
#
311+
# @return [ Result ] The response from the database.
312+
#
313+
# @since 2.1.0
314+
def delete_one(filter = nil)
315+
find(filter).delete_one
316+
end
317+
318+
# Remove documents from the collection.
319+
#
320+
# @example Remove multiple documents from the collection.
321+
# collection.delete_many
322+
#
323+
# @param [ Hash ] filter The filter to use.
324+
#
325+
# @return [ Result ] The response from the database.
326+
#
327+
# @since 2.1.0
328+
def delete_many(filter = nil)
329+
find(filter).delete_many
330+
end
331+
332+
# Replaces a single document in the collection with the new document.
333+
#
334+
# @example Replace a single document.
335+
# collection.replace_one({ name: 'test' }, { name: 'test1' })
336+
#
337+
# @param [ Hash ] filter The filter to use.
338+
# @param [ Hash ] replacement The replacement document..
339+
# @param [ Hash ] options The options.
340+
#
341+
# @option options [ true, false ] :upsert Whether to upsert if the
342+
# document doesn't exist.
343+
#
344+
# @return [ Result ] The response from the database.
345+
#
346+
# @since 2.1.0
347+
def replace_one(filter, replacement, options = {})
348+
find(filter).replace_one(replacement, options)
349+
end
350+
351+
# Update documents in the collection.
352+
#
353+
# @example Update multiple documents in the collection.
354+
# collection.update_many({ name: 'test'}, '$set' => { name: 'test1' })
355+
#
356+
# @param [ Hash ] filter The filter to use.
357+
# @param [ Hash ] update The update statement.
358+
# @param [ Hash ] options The options.
359+
#
360+
# @option options [ true, false ] :upsert Whether to upsert if the
361+
# document doesn't exist.
362+
#
363+
# @return [ Result ] The response from the database.
364+
#
365+
# @since 2.1.0
366+
def update_many(filter, update, options = {})
367+
find(filter).update_many(update, options)
368+
end
369+
370+
# Update a single document in the collection.
371+
#
372+
# @example Update a single document in the collection.
373+
# collection.update_one({ name: 'test'}, '$set' => { name: 'test1'})
374+
#
375+
# @param [ Hash ] filter The filter to use.
376+
# @param [ Hash ] update The update statement.
377+
# @param [ Hash ] options The options.
378+
#
379+
# @option options [ true, false ] :upsert Whether to upsert if the
380+
# document doesn't exist.
381+
#
382+
# @return [ Result ] The response from the database.
383+
#
384+
# @since 2.1.0
385+
def update_one(filter, update, options = {})
386+
find(filter).update_one(update, options)
387+
end
388+
389+
# Finds a single document in the database via findAndModify and deletes
390+
# it, returning the original document.
391+
#
392+
# @example Find one document and delete it.
393+
# collection.find_one_and_delete(name: 'test')
394+
#
395+
# @param [ Hash ] filter The filter to use.
396+
# @param [ Hash ] options The options.
397+
#
398+
# @option options [ Integer ] :max_time_ms The maximum amount of time to allow the command
399+
# to run in milliseconds.
400+
# @option options [ Hash ] :projection The fields to include or exclude in the returned doc.
401+
# @option options [ Hash ] :sort The key and direction pairs by which the result set
402+
# will be sorted.
403+
#
404+
# @return [ BSON::Document, nil ] The document, if found.
405+
#
406+
# @since 2.1.0
407+
def find_one_and_delete(filter, options = {})
408+
find(filter, options).find_one_and_delete
409+
end
410+
411+
# Finds a single document via findAndModify and updates it, returning the original doc unless
412+
# otherwise specified.
413+
#
414+
# @example Find a document and update it, returning the original.
415+
# collection.find_one_and_update({ name: 'test' }, { "$set" => { name: 'test1' }})
416+
#
417+
# @example Find a document and update it, returning the updated document.
418+
# collection.find_one_and_update({ name: 'test' }, { "$set" => { name: 'test1' }}, :return_document => :after)
419+
#
420+
# @param [ Hash ] filter The filter to use.
421+
# @param [ BSON::Document ] update The update statement.
422+
# @param [ Hash ] options The options.
423+
#
424+
# @option options [ Integer ] :max_time_ms The maximum amount of time to allow the command
425+
# to run in milliseconds.
426+
# @option options [ Hash ] :projection The fields to include or exclude in the returned doc.
427+
# @option options [ Hash ] :sort The key and direction pairs by which the result set
428+
# will be sorted.
429+
# @option options [ Symbol ] :return_document Either :before or :after.
430+
# @option options [ true, false ] :upsert Whether to upsert if the document doesn't exist.
431+
#
432+
# @return [ BSON::Document ] The document.
433+
#
434+
# @since 2.1.0
435+
def find_one_and_update(filter, update, options = {})
436+
find(filter, options).find_one_and_update(update, options)
437+
end
438+
439+
# Finds a single document and replaces it, returning the original doc unless
440+
# otherwise specified.
441+
#
442+
# @example Find a document and replace it, returning the original.
443+
# collection.find_one_and_replace({ name: 'test' }, { name: 'test1' })
444+
#
445+
# @example Find a document and replace it, returning the new document.
446+
# collection.find_one_and_replace({ name: 'test' }, { name: 'test1' }, :return_document => :after)
447+
#
448+
# @param [ Hash ] filter The filter to use.
449+
# @param [ BSON::Document ] replacement The replacement document.
450+
# @param [ Hash ] options The options.
451+
#
452+
# @option options [ Integer ] :max_time_ms The maximum amount of time to allow the command
453+
# to run in milliseconds.
454+
# @option options [ Hash ] :projection The fields to include or exclude in the returned doc.
455+
# @option options [ Hash ] :sort The key and direction pairs by which the result set
456+
# will be sorted.
457+
# @option options [ Symbol ] :return_document Either :before or :after.
458+
# @option options [ true, false ] :upsert Whether to upsert if the document doesn't exist.
459+
#
460+
# @return [ BSON::Document ] The document.
461+
#
462+
# @since 2.1.0
463+
def find_one_and_replace(filter, replacement, options = {})
464+
find(filter, options).find_one_and_update(replacement, options)
465+
end
466+
215467
# Get the fully qualified namespace of the collection.
216468
#
217469
# @example Get the fully qualified namespace.

lib/mongo/collection/view/aggregation.rb

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,15 @@ class Aggregation
3737
# Delegate necessary operations to the collection.
3838
def_delegators :collection, :database
3939

40+
# Options mapping for an aggregation.
41+
#
42+
# @since 2.1.0
43+
OPTIONS_MAP = {
44+
:allow_disk_use => :allowDiskUse,
45+
:max_time_ms => :maxTimeMS,
46+
:explain => :explain
47+
}
48+
4049
# Set to true if disk usage is allowed during the aggregation.
4150
#
4251
# @example Set disk usage flag.
@@ -49,7 +58,7 @@ class Aggregation
4958
#
5059
# @since 2.0.0
5160
def allow_disk_use(value = nil)
52-
configure(:allowDiskUse, value)
61+
configure(__method__, value)
5362
end
5463

5564
# Initialize the aggregation for the provided collection view, pipeline
@@ -89,11 +98,28 @@ def aggregate_spec
8998
:selector => {
9099
:aggregate => collection.name,
91100
:pipeline => pipeline,
92-
:cursor => view.batch_size ? { :batchSize => view.batch_size } : {}
93-
}.merge!(options)
101+
:cursor => cursor,
102+
}.merge!(agg_options)
94103
}
95104
end
96105

106+
def agg_options
107+
@agg_options ||= options.each.reduce({}) do |opts, (key, value)|
108+
OPTIONS_MAP[key] ? opts.merge!(OPTIONS_MAP[key] => value) : opts
109+
end
110+
end
111+
112+
def cursor
113+
if options[:use_cursor] == true || options[:use_cursor].nil?
114+
batch_size_doc
115+
end
116+
end
117+
118+
def batch_size_doc
119+
(value = options[:batch_size] || view.batch_size) ?
120+
{ :batchSize => value } : {}
121+
end
122+
97123
def explain_options
98124
{ :explain => true }
99125
end

0 commit comments

Comments
 (0)