ZetCode

MongoDB Ruby

最后修改日期 2021 年 1 月 28 日

在本教程中,我们将展示如何在 Ruby 中使用 MongoDB。 ZetCode 上有一个简明的 Ruby 教程

MongoDB 中的 记录 是一个文档,它是由字段和值对组成的数据结构。 MongoDB 文档 类似于 JSON 对象。 字段的值可以包括其他文档、数组和文档数组。 MongoDB 将文档存储在集合中。 集合 类似于关系数据库中的表,文档类似于行。

$ sudo gem install mongo

使用 sudo gem install mongo 命令安装 MongoDB Ruby 驱动程序。

创建数据库

mongo 工具是一个与 MongoDB 交互的 JavaScript shell 界面,它为系统管理员提供了一个接口,也为开发人员提供了一种直接与数据库测试查询和操作的方式。

$ 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})

我们创建一个 testdb 数据库,并在 cars 集合中插入八个文档。

列出数据库集合

Mongo::Clientcollections 方法列出数据库中可用的集合。

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

该示例连接到 testdb 数据库并检索其所有集合。

require 'mongo'

我们包含 mongo 驱动程序。

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

默认的日志记录级别是 ::Logger::DEBUG,其中包括许多调试信息。 为了使我们的输出更具可读性,我们选择 ::Logger::FATAL 调试级别。

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

使用 Mongo::Client 连接到 MongoDB 服务器。 我们指定 URL 和数据库名称。 27017 是 MongoDB 服务器监听的默认端口。

client.collections.each { |coll| puts coll.name }

我们遍历集合列表,并将其名称打印到控制台。

client.close

最后,我们关闭连接。 通常,不建议应用程序调用 close。 连接很昂贵,并且会被重用。 但是,由于它是一个一次性程序,而不是一个重用连接的长时间运行的应用程序,因此我们确实调用该方法。

$ ./list_collections.rb
cars

服务器选择超时

:server_selection_timeout 是选择服务器进行操作的超时时间(以秒为单位)。 当我们无法连接到数据库服务器时,会引发 Mongo::Error::NoServerAvailable

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

该示例具有错误的端口号。 默认情况下,服务器选择超时时间为 30 秒。 我们将其设置为 5 秒。

rescue Mongo::Error::NoServerAvailable => e

当连接未建立且超时已过期时,会抛出 Mongo::Error::NoServerAvailable

$ ./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.
...

调试日志记录级别在尝试连接到服务器时提供这些消息。

数据库统计信息

dbstats 命令获取数据库的统计信息。

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

该示例连接到 testdb 数据库并显示其统计信息。 使用数据库对象的 command 方法来执行命令。

db = client.use("testdb")

use 方法选择 testdb 数据库。

db.command({"dbstats" => 1}).documents[0].each do |key, value|

    puts "#{key}: #{value}"
end

command 方法执行 dbstats 命令并解析返回的哈希值。

$ ./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

读取数据

find 方法在集合中查找文档。

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

在该示例中,我们遍历 cars 集合的所有数据。

client[:cars].find.each { |doc| puts doc }

传递一个空查询会返回所有文档。 我们使用 each 方法遍历 :cars 集合的文档。

$ ./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

计数文档

count 方法返回集合中匹配文档的数量。

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

该示例计算 :cars 集合中的文档数量。

docs = client[:cars].find

我们从 cars 集合中检索所有文档。

puts "There are #{docs.count} documents"

我们打印返回的文档数量。

$ ./count_documents.rb
There are 8 documents

读取一个文档

find 方法接受一个可选的 filter 参数,该参数用于过滤传入的数据。

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

该示例从 :cars 集合中读取一个文档。

client[:cars].find(:name => 'Volkswagen').each do |doc|

find 方法仅显示包含大众汽车的文档。

$ ./read_one.rb
{"_id"=>BSON::ObjectId('60129647ce69ba1028119249'), "name"=>"Volkswagen", "price"=>21600.0}

查询运算符

可以使用 $gt$lt$ne 等 MongoDB 查询运算符来过滤数据。

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

该示例打印价格低于 30000 的所有文档,然后打印价格高于 30000 的所有文档。

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

$lt 运算符用于获取价格低于 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}

$and$or 逻辑运算符可用于组合多个表达式。

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

该示例同时介绍了 $or$and 运算符。

puts client[:cars].find('$or' => [{:name => "Audi"}, {:name => "Skoda" }]).to_a

$or 运算符用于返回名称为奥迪或斯柯达的文档。

puts client[:cars].find('$and' => [{:price => {'$gt' => 20000}},
    {:price => {'$lt' => 50000 }}]).to_a

$and 运算符检索价格在 20000 到 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)

投影确定要包含或排除结果集中每个文档中的哪些字段。

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

该示例从输出中排除了 _id 字段。

cursor = client[:cars].find({}, { :projection => {:_id => 0} })

我们在 find 方法的第二个参数中指定 :projection 选项。

$ ./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}

_id 尚未包含。

限制数据输出

limit 方法指定要返回的文档数量,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

该示例从 testdb.cars 集合中读取数据,跳过前两个文档,并将输出限制为五个文档。

docs = client[:cars].find().skip(2).limit(5)

skip 方法跳过前两个文档,limit 方法将输出限制为五个文档。

$ ./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}

聚合

聚合计算集合中数据的聚合值。

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 }

该示例计算集合中所有汽车的价格。

agr = [{"$group" => {:_id => 1, :all => { "$sum" => "$price" } }}];

$sum 运算符计算并返回数值的总和。$group 运算符根据指定的标识符表达式对输入文档进行分组,并对每个组应用指定的累加器表达式。$sum 运算符用于计数。

client[:cars].aggregate(agr).each { |doc| puts doc }

aggregate 方法将聚合操作应用于 cars 集合。

$ ./sum_all_cars.rb
{"_id"=>1, "all"=>581769.0}

所有价格的总和为 581769。

我们可以使用 $match 运算符来选择要聚合的特定汽车。

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

该示例计算奥迪和沃尔沃汽车价格的总和。

agr = [{"$match" => {"$or" => [ { :name => "Audi" }, { :name => "Volvo" }]}},
       {"$group" => {:_id => 1, :sumOfTwo => { "$sum" => "$price" } }}];

该表达式使用 $match$or$group$sum 运算符来完成任务。

$ ./sum_two_cars.rb
{"_id"=>1, "sumOfTwo"=>81642.0}

两辆车的总价为 81642。

插入文档

insert_one 方法将单个文档插入到集合中。

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 => BSON::ObjectId.new, :name => "Toyota", :price => 37600 }

client[:cars].insert_one doc

client.close

该示例将一辆汽车插入到 cars 集合中。

doc = { :_id => BSON::ObjectId.new, :name => "Toyota", :price => 37600 }

这是要插入的文档。

client[:cars].insert_one doc

insert_one 方法将文档插入到集合中。

> db.cars.find({name:"Toyota"})
{ "_id" : ObjectId("60129c9e4c9be8109fc53ddc"), "name" : "Toyota", "price" : 37600 }

我们使用 mongo 工具确认插入。

插入多个文档

insert_many 方法将多个文档插入到集合中。

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

该示例创建一个 continents 集合并将六个文档插入其中。

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' }
])

一个包含六条记录的数组使用 insert_many 方法插入到新集合中。 BSON::ObjectId.new 创建一个新的 ObjectID,它是一个用于标识文档的唯一值,而不是整数。

puts "#{result.inserted_count} documents were inserted"

来自返回结果的 inserted_count 给出了成功插入的文档数。

> 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" }

continents 集合已成功创建。

修改文档

delete_one 方法用于删除文档,update_one 用于更新文档。

modify.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].delete_one({:name => "Skoda"})
client[:cars].update_one({:name => "Audi"}, '$set' => {:price => 52000})

client.close

该示例删除包含 Skoda 的文档并更新 Audi 的价格。

client[:cars].delete_one({:name => "Skoda"})

delete_one 删除 Skoda 的文档。

client[:cars].update_one({:name => "Audi"}, '$set' => {:price => 52000})

奥迪的价格使用 update_one 方法更改为 52000。 $set 运算符用于更改价格。

> 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 }

我们使用 mongo 工具确认更改。

在本教程中,我们使用 MongoDB 和 Ruby。