MongoDB Ruby tutorial

In this tutorial, 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.

Installing MongoDB

The following command can be used to install MongoDB on a Debian-based Linux.

$ sudo apt-get install mongodb

The command installs the necessary packages that come with MongoDB.

$ sudo service mongodb status
mongodb start/running, process 975

With the sudo service mongodb status command we check the status of the mongodb server.

$ sudo service mongodb start
mongodb start/running, process 6448

The mongodb server is started with the sudo service mongodb start command.

$ 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: 2.4.9
connecting to: testdb
> 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.

list_collections.rb
#!/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 
test
cars

This is a sample output of the list_collections.rb program.

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.

server_selection_timeout.rb
#!/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, [2016-05-02T15:32:20.231750 #8070] DEBUG -- : MONGODB | Adding 127.0.0.1:2717 to the cluster.
D, [2016-05-02T15:32:20.232486 #8070] DEBUG -- : MONGODB | Connection refused - connect(2)
D, [2016-05-02T15:32:20.732627 #8070] DEBUG -- : MONGODB | Connection refused - connect(2)
D, [2016-05-02T15:32:21.232724 #8070] DEBUG -- : MONGODB | Connection refused - connect(2)
...

The debug logging level provides these messages while trying to connect to the server.

Database statistics

The dbstats command gets statistics of a database.

dbstats.rb
#!/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: 4
objects: 21
avgObjSize: 43.23809523809524
dataSize: 908
storageSize: 16384
numExtents: 4
indexes: 2
indexSize: 16352
fileSize: 201326592
nsSizeMB: 16
dataFileVersion: {"major"=>4, "minor"=>5}
ok: 1.0

This is the output of the dbstats.rb program.

Reading data

The find method finds documents in the collection.

read_all.rb
#!/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"=>1, "name"=>"Audi", "price"=>52642}
{"_id"=>2, "name"=>"Mercedes", "price"=>57127}
{"_id"=>3, "name"=>"Skoda", "price"=>9000}
{"_id"=>4, "name"=>"Volvo", "price"=>29000}
{"_id"=>5, "name"=>"Bentley", "price"=>350000}
{"_id"=>6, "name"=>"Citroen", "price"=>21000}
{"_id"=>7, "name"=>"Hummer", "price"=>41400}
{"_id"=>8, "name"=>"Volkswagen", "price"=>21600}

This is the output of the read_all.rb example.

Counting documents

The count method returns the number of matching documents in the collection.

count_documents.rb
#!/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

There are eight documents in the cars collection.

Reading one document

The find method takes an optional filter parameter which is used to filter the incoming data.

read_one.rb
#!/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"=>8, "name"=>"Volkswagen", "price"=>21600}

This is the output of the example.

Query operators

It is possible to filter data using MongoDB query operators such as $gt, $lt, or $ne.

read_op.rb
#!/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 30,000 and later all documents whose car prices' are greater than 30,000.

puts client[:cars].find("price" => {'$lt' => 30000}).to_a

The $lt operator is used to get cars whose prices are lower than 30,000.

$ ./read_op.rb 
{"_id"=>3, "name"=>"Skoda", "price"=>9000}
{"_id"=>4, "name"=>"Volvo", "price"=>29000}
{"_id"=>6, "name"=>"Citroen", "price"=>21000}
{"_id"=>8, "name"=>"Volkswagen", "price"=>21600}
**************************
{"_id"=>1, "name"=>"Audi", "price"=>52642}
{"_id"=>2, "name"=>"Mercedes", "price"=>57127}
{"_id"=>5, "name"=>"Bentley", "price"=>350000}
{"_id"=>7, "name"=>"Hummer", "price"=>41400}

This is the output of the example.

The $and and $or logical operators can be used to combine multiple expressions.

read_and_or.rb
#!/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 20,000 and 50,000.

$ ./read_and_or.rb 
{"_id"=>1, "name"=>"Audi", "price"=>52642}
{"_id"=>3, "name"=>"Skoda", "price"=>9000}
*************************************
{"_id"=>4, "name"=>"Volvo", "price"=>29000}
{"_id"=>6, "name"=>"Citroen", "price"=>21000}
{"_id"=>7, "name"=>"Hummer", "price"=>41400}
{"_id"=>8, "name"=>"Volkswagen", "price"=>21600}

This is the output of the example.

Projections

Projections determine which fields to include or exclude from each document in the result set.

projection.rb
#!/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}
{"name"=>"Mercedes", "price"=>57127}
{"name"=>"Skoda", "price"=>9000}
{"name"=>"Volvo", "price"=>29000}
{"name"=>"Bentley", "price"=>350000}
{"name"=>"Citroen", "price"=>21000}
{"name"=>"Hummer", "price"=>41400}
{"name"=>"Volkswagen", "price"=>21600}

This is the output for the example. 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.

skip_limit.rb
#!/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"=>3, "name"=>"Skoda", "price"=>9000}
{"_id"=>4, "name"=>"Volvo", "price"=>29000}
{"_id"=>5, "name"=>"Bentley", "price"=>350000}
{"_id"=>6, "name"=>"Citroen", "price"=>21000}
{"_id"=>7, "name"=>"Hummer", "price"=>41400}

This is the output of the example.

Aggregations

Aggregations calculate aggregate values for the data in a collection.

sum_all_cars.rb
#!/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"=>609727}

The sum of all prices is 619,369.

We can use the $match operator to select specific cars to aggregate.

sum_two_cars.rb
#!/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"=>81000}

The sum of the two cars' prices is 81,642.

Inserting a document

The insert_one method inserts a single document into a collection.

insert_doc.rb
#!/usr/bin/ruby

require 'mongo'

Mongo::Logger.logger.level = ::Logger::FATAL

client = Mongo::Client.new([ '127.0.0.1:27017' ], :database => 'testdb')

doc = { :_id => 9, :name => "Toyota", :price => 37600 } 

client[:cars].insert_one doc 

client.close

The example inserts one car into the cars collection.

doc = { :_id => 9, :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({_id:9})
{ "_id" : 9, "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.

create_collection.rb
#!/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("57263c0f81365b266b17358c"), "name" : "Africa" }
{ "_id" : ObjectId("57263c0f81365b266b17358d"), "name" : "America" }
{ "_id" : ObjectId("57263c0f81365b266b17358e"), "name" : "Antarctica" }
{ "_id" : ObjectId("57263c0f81365b266b17358f"), "name" : "Australia" }
{ "_id" : ObjectId("57263c0f81365b266b173590"), "name" : "Asia" }
{ "_id" : ObjectId("57263c0f81365b266b173591"), "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.

mofify.js
#!/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 52,000 with the update_one method. The $set operator is used to change the price.

> db.cars.find()
{ "_id" : 1, "name" : "Audi", "price" : 52000 }
{ "_id" : 2, "name" : "Mercedes", "price" : 57127 }
{ "_id" : 4, "name" : "Volvo", "price" : 29000 }
{ "_id" : 5, "name" : "Bentley", "price" : 350000 }
{ "_id" : 6, "name" : "Citroen", "price" : 21000 }
{ "_id" : 7, "name" : "Hummer", "price" : 41400 }
{ "_id" : 8, "name" : "Volkswagen", "price" : 21600 }
{ "_id" : 9, "name" : "Toyota", "price" : 37600 }

We confirm the changes with the mongo tool.

In this tutorial, we have worked with MongoDB and Ruby.