Ruby MongoDB
last modified October 18, 2023
In this article we show how to work with MongoDB in Ruby. There is a concise Ruby tutorial on ZetCode.
MongoDB is a NoSQL cross-platform document-oriented database. It is one of the most popular databases available. MongoDB is developed by MongoDB Inc. and is published as free and open-source software.
A record in MongoDB is a document, which is a data structure composed of field and value pairs. MongoDB documents are similar to JSON objects. The values of fields may include other documents, arrays, and arrays of documents. MongoDB stores documents in collections. Collections are analogous to tables in relational databases and documents to rows.
MongoDB represents JSON documents in binary-encoded format called BSON behind the scenes. BSON extends the JSON model to provide additional data types, ordered fields, and to be efficient for encoding and decoding within different languages.
$ sudo gem install mongo
The MongoDB Ruby driver is installed with sudo gem install mongo
command.
Creating a database
The mongo
tool is an interactive JavaScript shell interface to
MongoDB, which provides an interface for systems administrators as well as
a way for developers to test queries and operations directly with the database.
$ mongo testdb MongoDB shell version v4.4.3 ... > db testdb > db.cars.insert({name: "Audi", price: 52642}) > db.cars.insert({name: "Mercedes", price: 57127}) > db.cars.insert({name: "Skoda", price: 9000}) > db.cars.insert({name: "Volvo", price: 29000}) > db.cars.insert({name: "Bentley", price: 350000}) > db.cars.insert({name: "Citroen", price: 21000}) > db.cars.insert({name: "Hummer", price: 41400}) > db.cars.insert({name: "Volkswagen", price: 21600})
We create a testdb
database and insert eight documents in the
cars
collection.
Listing database collections
The Mongo::Client's
collections
method lists available
collections in a database.
#!/usr/bin/ruby require 'mongo' Mongo::Logger.logger.level = ::Logger::FATAL client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb') client.collections.each { |coll| puts coll.name } client.close
The example connects to the testdb
database and
retrieves all its collections.
require 'mongo'
We include the mongo
driver.
Mongo::Logger.logger.level = ::Logger::FATAL
The default logging level is ::Logger::DEBUG
which includes many
debugging information. For our output to be more readable, we choose
::Logger::FATAL
debugging level.
client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb')
Mongo::Client
is used to connect to the MongoDB server.
We specify the URL and the database name. The 27017 is the default port
on which the MongoDB server listens.
client.collections.each { |coll| puts coll.name }
We go through the list of collections and print their names to the console.
client.close
At the end, we close the connection. Generally, it is not recommended for
applications to call close
. The connections are expensive and are
being reused. But since it is a one-off program and not a long running application
which reuses connections, we do call the method.
$ ./list_collections.rb cars
Server selection timeout
The :server_selection_timeout
is the timeout in seconds for
selecting a server for an operation. Mongo::Error::NoServerAvailable
is raised when we cannot connect to the database server.
#!/usr/bin/ruby require 'mongo' Mongo::Logger.logger.level = ::Logger::DEBUG begin client = Mongo::Client.new([ '127.0.0.1:2717' ], :database => "testdb", :server_selection_timeout => 5) client[:cars].find.each { |doc| puts doc } client.close rescue Mongo::Error::NoServerAvailable => e p "Cannot connect to the server" p e end
The example has a wrong port number. By default, the server selection timeout is thirty seconds. We set it to five seconds.
rescue Mongo::Error::NoServerAvailable => e
Mongo::Error::NoServerAvailable
is thrown when the connection is not established
and the timeout has expired.
$ ./server_selection_timeout.rb D, [2021-01-28T11:51:05.109837 #200040] DEBUG -- : MONGODB | Topology type 'unknown' initializing. D, [2021-01-28T11:51:05.110081 #200040] DEBUG -- : MONGODB | There was a change in the members of the 'Unknown' topology. D, [2021-01-28T11:51:05.110254 #200040] DEBUG -- : MONGODB | Server 127.0.0.1:2717 initializing. ...
The debug logging level provides these messages while trying to connect to the server.
Database statistics
The dbstats
command gets statistics of a database.
#!/usr/bin/ruby require 'mongo' Mongo::Logger.logger.level = ::Logger::FATAL client = Mongo::Client.new([ '127.0.0.1:27017' ]) db = client.use("testdb") db.command({"dbstats" => 1}).documents[0].each do |key, value| puts "#{key}: #{value}" end client.close
The example connects to the testdb
database and shows its
statistics. The command
method of the database object is used to
execute a command.
db = client.use("testdb")
The use
method selects the testdb
database.
db.command({"dbstats" => 1}).documents[0].each do |key, value| puts "#{key}: #{value}" end
The command
method executes the dbstats
command and
parses the returned hash value.
$ ./dbstats.rb db: testdb collections: 1 views: 0 objects: 8 avgObjSize: 54.5 dataSize: 436.0 storageSize: 36864.0 indexes: 1 indexSize: 36864.0 totalSize: 73728.0 scaleFactor: 1.0 fsUsedSize: 222740852736.0 fsTotalSize: 483050881024.0 ok: 1.0
Reading data
The find
method finds documents in the collection.
#!/usr/bin/ruby require 'mongo' Mongo::Logger.logger.level = ::Logger::FATAL client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb') client[:cars].find.each { |doc| puts doc } client.close
In the example, we iterate over all data of the cars
collection.
client[:cars].find.each { |doc| puts doc }
Passing an empty query returns all documents. We iterate through the documents
of the :cars
collection using the each
method.
$ ./read_all.rb {"_id"=>BSON::ObjectId('60129621ce69ba1028119242'), "name"=>"Audi", "price"=>52642.0} {"_id"=>BSON::ObjectId('60129627ce69ba1028119243'), "name"=>"Mercedes", "price"=>57127.0} {"_id"=>BSON::ObjectId('6012962fce69ba1028119244'), "name"=>"Skoda", "price"=>9000.0} {"_id"=>BSON::ObjectId('60129634ce69ba1028119245'), "name"=>"Volvo", "price"=>29000.0} {"_id"=>BSON::ObjectId('6012963ace69ba1028119246'), "name"=>"Bentley", "price"=>350000.0} {"_id"=>BSON::ObjectId('6012963ece69ba1028119247'), "name"=>"Citroen", "price"=>21000.0} {"_id"=>BSON::ObjectId('60129643ce69ba1028119248'), "name"=>"Hummer", "price"=>41400.0} {"_id"=>BSON::ObjectId('60129647ce69ba1028119249'), "name"=>"Volkswagen", "price"=>21600.0
Counting documents
The count
method returns the number of matching
documents in the collection.
#!/usr/bin/ruby require 'mongo' Mongo::Logger.logger.level = ::Logger::FATAL client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb') docs = client[:cars].find puts "There are #{docs.count} documents" client.close
The example counts the number of documents in the :cars
collection.
docs = client[:cars].find
We retrieve all documents from the cars
collection.
puts "There are #{docs.count} documents"
We print the number of returned documents.
$ ./count_documents.rb There are 8 documents
Reading one document
The find
method takes an optional filter parameter which is used to
filter the incoming data.
#!/usr/bin/ruby require 'mongo' Mongo::Logger.logger.level = ::Logger::FATAL client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb') client[:cars].find(:name => 'Volkswagen').each do |doc| puts doc end client.close
The example reads one document from the :cars
collection.
client[:cars].find(:name => 'Volkswagen').each do |doc|
The find
method only shows the document containing the Volkswagen
car.
$ ./read_one.rb {"_id"=>BSON::ObjectId('60129647ce69ba1028119249'), "name"=>"Volkswagen", "price"=>21600.0}
Query operators
It is possible to filter data using MongoDB query operators such as
$gt
, $lt
, or $ne
.
#!/usr/bin/ruby require 'mongo' Mongo::Logger.logger.level = ::Logger::FATAL client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb') puts client[:cars].find("price" => {'$lt' => 30000}).to_a puts "**************************" client[:cars].find("price" => {'$gt' => 30000}).each do |doc| puts doc end client.close
The example prints all documents whose car prices' are lower than 30000 and later all documents whose car prices' are greater than 30000.
puts client[:cars].find("price" => {'$lt' => 30000}).to_a
The $lt
operator is used to get cars whose prices are lower
than 30000.
$ ./read_op.rb {"_id"=>BSON::ObjectId('6012962fce69ba1028119244'), "name"=>"Skoda", "price"=>9000.0} {"_id"=>BSON::ObjectId('60129634ce69ba1028119245'), "name"=>"Volvo", "price"=>29000.0} {"_id"=>BSON::ObjectId('6012963ece69ba1028119247'), "name"=>"Citroen", "price"=>21000.0} {"_id"=>BSON::ObjectId('60129647ce69ba1028119249'), "name"=>"Volkswagen", "price"=>21600.0} ************************** {"_id"=>BSON::ObjectId('60129621ce69ba1028119242'), "name"=>"Audi", "price"=>52642.0} {"_id"=>BSON::ObjectId('60129627ce69ba1028119243'), "name"=>"Mercedes", "price"=>57127.0} {"_id"=>BSON::ObjectId('6012963ace69ba1028119246'), "name"=>"Bentley", "price"=>350000.0} {"_id"=>BSON::ObjectId('60129643ce69ba1028119248'), "name"=>"Hummer", "price"=>41400.0}
The $and
and $or
logical operators can be used to
combine multiple expressions.
#!/usr/bin/ruby require 'mongo' Mongo::Logger.logger.level = ::Logger::FATAL client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb') puts client[:cars].find('$or' => [{:name => "Audi"}, {:name => "Skoda" }]).to_a puts "*************************************" puts client[:cars].find('$and' => [{:price => {'$gt' => 20000}}, {:price => {'$lt' => 50000 }}]).to_a client.close
The example presents both $or
and $and
operators.
puts client[:cars].find('$or' => [{:name => "Audi"}, {:name => "Skoda" }]).to_a
The $or
operator is used to return documents whose names are either
Audi or Skoda.
puts client[:cars].find('$and' => [{:price => {'$gt' => 20000}}, {:price => {'$lt' => 50000 }}]).to_a
The $and
operator retrieves cars whose prices fall between 20000
and 50000.
$ ./read_and_or.rb {"_id"=>BSON::ObjectId('60129621ce69ba1028119242'), "name"=>"Audi", "price"=>52642.0} {"_id"=>BSON::ObjectId('6012962fce69ba1028119244'), "name"=>"Skoda", "price"=>9000.0} ************************************* {"_id"=>BSON::ObjectId('60129634ce69ba1028119245'), "name"=>"Volvo", "price"=>29000.0} {"_id"=>BSON::ObjectId('6012963ece69ba1028119247'), "name"=>"Citroen", "price"=>21000.0} {"_id"=>BSON::ObjectId('60129643ce69ba1028119248'), "name"=>"Hummer", "price"=>41400.0} {"_id"=>BSON::ObjectId('60129647ce69ba1028119249'), "name"=>"Volkswagen", "price"=>21600.0}
Projections
Projections determine which fields to include or exclude from each document in the result set.
#!/usr/bin/ruby require 'mongo' Mongo::Logger.logger.level = ::Logger::FATAL client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb') cursor = client[:cars].find({}, { :projection => {:_id => 0} }) cursor.each { |doc| puts doc } client.close
The example excludes the _id
field from the output.
cursor = client[:cars].find({}, { :projection => {:_id => 0} })
We specify the :projection
option in the second parameter of the
find
method.
$ ./projection.rb {"name"=>"Audi", "price"=>52642.0} {"name"=>"Mercedes", "price"=>57127.0} {"name"=>"Skoda", "price"=>9000.0} {"name"=>"Volvo", "price"=>29000.0} {"name"=>"Bentley", "price"=>350000.0} {"name"=>"Citroen", "price"=>21000.0} {"name"=>"Hummer", "price"=>41400.0} {"name"=>"Volkswagen", "price"=>21600.0}
The _id
has not been included.
Limiting data output
The limit
method specifies the number of documents to be returned
and the skip
method the number of documents to skip.
#!/usr/bin/ruby require 'mongo' Mongo::Logger.logger.level = ::Logger::FATAL client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb') docs = client[:cars].find().skip(2).limit(5) docs.each do |doc| puts doc end client.close
The example reads from the testdb.cars
collection, skips the first
two documents, and limits the output to five documents.
docs = client[:cars].find().skip(2).limit(5)
The skip
method skips the first two documents and the
limit
method limits the output to five documents.
$ ./skip_limit.rb {"_id"=>BSON::ObjectId('6012962fce69ba1028119244'), "name"=>"Skoda", "price"=>9000.0} {"_id"=>BSON::ObjectId('60129634ce69ba1028119245'), "name"=>"Volvo", "price"=>29000.0} {"_id"=>BSON::ObjectId('6012963ace69ba1028119246'), "name"=>"Bentley", "price"=>350000.0} {"_id"=>BSON::ObjectId('6012963ece69ba1028119247'), "name"=>"Citroen", "price"=>21000.0} {"_id"=>BSON::ObjectId('60129643ce69ba1028119248'), "name"=>"Hummer", "price"=>41400.0}
Aggregations
Aggregations calculate aggregate values for the data in a collection.
#!/usr/bin/ruby require 'mongo' Mongo::Logger.logger.level = ::Logger::FATAL agr = [{"$group" => {:_id => 1, :all => { "$sum" => "$price" } }}]; client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb') client[:cars].aggregate(agr).each { |doc| puts doc }
The example calculates the prices of all cars in the collection.
agr = [{"$group" => {:_id => 1, :all => { "$sum" => "$price" } }}];
The $sum
operator calculates and returns the sum of numeric values.
The $group
operator groups input documents by a specified identifier
expression and applies the accumulator expression(s), if specified, to each group.
client[:cars].aggregate(agr).each { |doc| puts doc }
The aggregate
method applies the aggregation operation on the
cars
collection.
$ ./sum_all_cars.rb {"_id"=>1, "all"=>581769.0}
The sum of all prices is 581769.
We can use the $match
operator to select specific cars to
aggregate.
#!/usr/bin/ruby require 'mongo' Mongo::Logger.logger.level = ::Logger::FATAL agr = [{"$match" => {"$or" => [ { :name => "Audi" }, { :name => "Volvo" }]}}, {"$group" => {:_id => 1, :sumOfTwo => { "$sum" => "$price" } }}]; client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb') client[:cars].aggregate(agr).each { |doc| puts doc } client.close
The example calculates the sum of prices of Audi and Volvo cars.
agr = [{"$match" => {"$or" => [ { :name => "Audi" }, { :name => "Volvo" }]}}, {"$group" => {:_id => 1, :sumOfTwo => { "$sum" => "$price" } }}];
The expression uses $match
, $or
, $group
,
and $sum
operators to do the task.
$ ./sum_two_cars.rb {"_id"=>1, "sumOfTwo"=>81642.0}
The sum of the two cars' prices is 81642.
Inserting a document
The insert_one
method inserts a single document into
a collection.
#!/usr/bin/ruby require 'mongo' Mongo::Logger.logger.level = ::Logger::FATAL client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb') doc = { :_id => BSON::ObjectId.new, :name => "Toyota", :price => 37600 } client[:cars].insert_one doc client.close
The example inserts one car into the cars
collection.
doc = { :_id => BSON::ObjectId.new, :name => "Toyota", :price => 37600 }
This is the document to be inserted.
client[:cars].insert_one doc
The insert_one
method inserts the document into
the collection.
> db.cars.find({name:"Toyota"}) { "_id" : ObjectId("60129c9e4c9be8109fc53ddc"), "name" : "Toyota", "price" : 37600 }
We confirm the insertion with the mongo
tool.
Inserting many documents
The insert_many
method inserts multiple documents into a collection.
#!/usr/bin/ruby require 'mongo' Mongo::Logger.logger.level = ::Logger::FATAL client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb') result = client[:continents].insert_many([ { :_id => BSON::ObjectId.new, :name => 'Africa' }, { :_id => BSON::ObjectId.new, :name => 'America' }, { :_id => BSON::ObjectId.new, :name => 'Antarctica' }, { :_id => BSON::ObjectId.new, :name => 'Australia' }, { :_id => BSON::ObjectId.new, :name => 'Asia' }, { :_id => BSON::ObjectId.new, :name => 'Europe' } ]) puts "#{result.inserted_count} documents were inserted" client.close
The example creates a continents collection and inserts six documents into it.
result = client[:continents].insert_many([ { :_id => BSON::ObjectId.new, :name => 'Africa' }, { :_id => BSON::ObjectId.new, :name => 'America' }, { :_id => BSON::ObjectId.new, :name => 'Antarctica' }, { :_id => BSON::ObjectId.new, :name => 'Australia' }, { :_id => BSON::ObjectId.new, :name => 'Asia' }, { :_id => BSON::ObjectId.new, :name => 'Europe' } ])
An array of six records is inserted into the new collection with the
insert_many
method. BSON::ObjectId.new
creates a new
ObjectID, which is a unique value used to identify documents instead of
integers.
puts "#{result.inserted_count} documents were inserted"
The inserted_count
from the returned result gives the number of
successfully inserted documents.
> db.continents.find() { "_id" : ObjectId("60129bbd4c9be8102bc1ee37"), "name" : "Africa" } { "_id" : ObjectId("60129bbd4c9be8102bc1ee38"), "name" : "America" } { "_id" : ObjectId("60129bbd4c9be8102bc1ee39"), "name" : "Antarctica" } { "_id" : ObjectId("60129bbd4c9be8102bc1ee3a"), "name" : "Australia" } { "_id" : ObjectId("60129bbd4c9be8102bc1ee3b"), "name" : "Asia" } { "_id" : ObjectId("60129bbd4c9be8102bc1ee3c"), "name" : "Europe" }
The continents
collection has been successfully created.
Modifying documents
The delete_one
method is used to delete a document and
update_one
to update a document.
#!/usr/bin/ruby require 'mongo' Mongo::Logger.logger.level = ::Logger::FATAL client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb') client[:cars].delete_one({:name => "Skoda"}) client[:cars].update_one({:name => "Audi"}, '$set' => {:price => 52000}) client.close
The example deletes a document containing Skoda and updates the price of Audi.
client[:cars].delete_one({:name => "Skoda"})
The delete_one
deletes the document of Skoda
.
client[:cars].update_one({:name => "Audi"}, '$set' => {:price => 52000})
The price of Audi is changed to 52000 with the update_one
method.
The $set
operator is used to change the price.
> db.cars.find() { "_id" : ObjectId("60129621ce69ba1028119242"), "name" : "Audi", "price" : 52000 } { "_id" : ObjectId("60129627ce69ba1028119243"), "name" : "Mercedes", "price" : 57127 } { "_id" : ObjectId("60129634ce69ba1028119245"), "name" : "Volvo", "price" : 29000 } { "_id" : ObjectId("6012963ace69ba1028119246"), "name" : "Bentley", "price" : 350000 } { "_id" : ObjectId("6012963ece69ba1028119247"), "name" : "Citroen", "price" : 21000 } { "_id" : ObjectId("60129643ce69ba1028119248"), "name" : "Hummer", "price" : 41400 } { "_id" : ObjectId("60129647ce69ba1028119249"), "name" : "Volkswagen", "price" : 21600 } { "_id" : ObjectId("60129c9e4c9be8109fc53ddc"), "name" : "Toyota", "price" : 37600 }
We confirm the changes with the mongo
tool.
In this article we have worked with MongoDB and Ruby.