mongo shell, ruby mongo dirver and mongoid reference
There’re two MongoDB drivers in Ruby: Ruby MongoDB Driver and Mongoid.
Ruby MongoDB Driver
’s syntax is very similiar with Mongo Shell
, which is meant to.
Mongoid
, on the other hand, trying to provide a familiar API to Active Record. Even though Mongoid
is built on top of Ruby MongoDB Driver
, they’re quite different in syntax. Plus Mongoid
includes extra features like validation and relations which are quite common in Rails.
I’m always lost in the syntax of these two drivers. So I wrote this article to help me out.
I’m going to show examples in Mongo Shell
, Ruby MongoDB Driver
and Mongoid
for the same MongoDB operations.
- Versions
- Test Data
- Connection
- Create Operation
- Exists
- Query Operation
- Arrays and Hashes
- Update Operation
- Field Update Operation
- Limit Fields
- Distinct
- Sort
- Others
Versions
Here are the versions information.
~$ mongod --version
# db version v3.2.11
# git version: 009580ad490190ba33d1c6253ebd8d91808923e4
# OpenSSL version: OpenSSL 1.0.2k 26 Jan 2017
# allocator: system
# modules: none
# build environment:
# distarch: x86_64
# target_arch: x86_64
~$ mongo --version
# MongoDB shell version: 3.2.11
~$ gem list | grep mongo
# mongo (2.4.1)
# mongoid (6.1.0)
Test Data
Mongo provides a very good test data for you to play with. You could import these data by following the steps.
We don’t need to import these data. But we need to keep it in mind. Basically, we should know:
- each document represent a restanrant;
- restaurant address is a hash;
- restaurants grades is an array;
- restaurants has borough, cuisine and restaurant_id.
[
{
"address": {
"building": "1007",
"coord": [ -73.856077, 40.848447 ],
"street": "Morris Park Ave",
"zipcode": "10462"
},
"borough": "Bronx",
"cuisine": "Bakery",
"grades": [
{ "date": { "$date": 1393804800000 }, "grade": "A", "score": 2 },
{ "date": { "$date": 1378857600000 }, "grade": "A", "score": 6 },
{ "date": { "$date": 1358985600000 }, "grade": "A", "score": 10 },
{ "date": { "$date": 1322006400000 }, "grade": "A", "score": 9 },
{ "date": { "$date": 1299715200000 }, "grade": "B", "score": 14 }
],
"name": "Morris Park Bake Shop",
"restaurant_id": "30075445"
},
// ...
]
Connection
mongo shell
~$ mongo 127.0.0.1:27017/test -u <dbuser> -p <dbpassword>
# if your DB instance is running in local and security was not enabled, simply type:
# ~$ mongo
ruby mongo driver
require 'mongo'
# output less codes from console
Mongo::Logger.logger.level = Logger::WARN
host = ["127.0.0.1:27017"]
# remove user and password if don't have
options = {
database: "test",
user: 'dbuser',
password: 'dbpassword'
}
client = Mongo::Client.new(host, options)
mongoid
Create a mongoid.yml
file with following content.
development:
clients:
default:
database: test
hosts:
- 127.0.0.1:27017
or
development:
clients:
default:
# remove user and password if don't have
uri: mongodb://<user>:<password>@127.0.0.1:27017/test
Then, connect to DB with following codes.
require 'mongoid'
Mongoid.load!("mongoid.yml", :development)
Create Operation
Mongo Shell
db.restaurants.insert({name: "my restaurants", borough: "Shenzhen", cuisine: "Soup" })
db.restaurants.insertMany([
{name: "my restaurants 01", borough: "Shenzhen", cuisine: "Soup" },
{name: "my restaurants 02", borough: "Shenzhen", cuisine: "Soup" }
])
Ruby Mongo Driver
client["restaurants"].insert_one({name: "my restaurants", borough: "Shenzhen", cuisine: "Soup"})
client["restaurants"].insert_many([
{name: "my restaurants 01", borough: "Shenzhen", cuisine: "Soup"},
{name: "my restaurants 02", borough: "Shenzhen", cuisine: "Soup"}
])
Mongoid
# note: Restanrant<class name> is singular!
class Restaurant
include Mongoid::Document
field :name, type: String
field :borough, type: String
field :cuisine, type: String
field :address, type: Hash
field :grades, type: Array
end
Restaurant.create({name: "my restaurants", borough: "Shenzhen", cuisine: "Soup"})
Restaurant.create([
{name: "my restaurants 01", borough: "Shenzhen", cuisine: "Soup"},
{name: "my restaurants 02", borough: "Shenzhen", cuisine: "Soup"}
])
Exists
mongo shell
db.generators.find({name: 'my restaurants 01'}).count()
Ruby Mongo Driver
client["restaurants"].find({name: "my restaurants 01"}).count()
Mongoid
Restaurant.where({name: "my restaurants 01"}).exists?
Query Operation
mongo shell
// == find by ID ==
db.restaurants.find(ObjectId('58f4baa603bb14a0c4306289'))
// === find one ===
db.restaurants.findOne({name: 'my restaurants'})
// === find many ==
db.restaurants.find({name: 'my restaurants'})
// === and ===
db.restaurants.find({name: 'my restaurants', borough: 'Shenzhen'})
// === or ===
db.restaurants.find({
$or: [ {name: 'my restaurants'}, { borough: 'Shenzhen'} ]
})
// === in ===
db.restaurants.find({
borough: { $in: ['Shenzhen', 'Bronx'] }
})
// === and & or ===
db.restaurants.find({
borough: 'Bronx',
$or: [{cuisine: 'Bakery'}, {name: 'my restaurants'}]
})
// == comparison ==
db.restaurants.find({
$or: [
{ borough: 'Bronx' },
{ restaurant_id: { $gt: '30075445' } }
]
})
ruby mongo driver
# == find by ID ==
client["restaurants"].find({ _id: BSON::ObjectId("58f4baa603bb14a0c4306289")})
# === find one ===
client["restaurants"].find({name: "my restaurants"}).first
# === find many ==
ary = client["restaurants"].find({name: "my restaurants"})
# === and ===
client["restaurants"].find({name: "my restaurants", borough: "Shenzhen"})
# === or ===
client["restaurants"].find(
"$or" => [ {name: "my restaurants"}, { borough: "Shenzhen"} ]
)
# === in ===
client["restaurants"].find(
borough: { "$in" => ["Shenzhen", "Bronx"] }
)
# === and & or ===
client["restaurants"].find({
borough: "Bronx",
"$or" => [ {cuisine: "Bakery"}, {name: "my restaurants"} ]
})
# == comparison ==
client["restaurants"].find({
"$or" => [
{ borough: "Bronx" },
{ restaurant_id: { "$gt" => "30075445" } }
]
})
mongoid
class Restaurant
include Mongoid::Document
field :name, type: String
field :borough, type: String
field :cuisine, type: String
field :address, type: Hash
field :grades, type: Array
end
# == find by ID ==
Restaurant.find("58f4baa603bb14a0c4306289")
# === find one ===
Restaurant.where({name: "my restaurants"}).first
# === find many ==
Restaurant.where({name: "my restaurants"})
# === and ===
Restaurant.where({name: "my restaurants", borough: "Shenzhen"})
# === or ===
Restaurant.where(
"$or" => [ {name: "my restaurants"}, { borough: "Shenzhen"} ]
)
# === in ===
Restaurant.where(
borough: { "$in" => ["Shenzhen", "Bronx"] }
)
# === and & or ===
Restaurant.where({
borough: "Bronx",
"$or" => [ {cuisine: "Bakery"}, {name: "my restaurants"} ]
})
# == comparison ==
Restaurant.where({
"$or" => [
{ borough: "Bronx" },
{ restaurant_id: { "$gt" => "30075445" } }
]
})
Restaurant.where({
"$or" => [
{ borough: "Bronx" },
{ :restaurant_id.gt => "30075445" }
]
})
Arrays and Hashes
mongo shell
// ==== hash ====
db.restaurants.find({ 'address.building': '1007' })
// ==== array ====
db.restaurants.find({ 'address.coord.0': -73.856077 })
// == array size ==
db.restaurants.find({ 'address.coord': {$size: 2} })
// = array & hash =
db.restaurants.find({ grades: {$elemMatch: {grade: 'C', score: 10} } })
ruby mongo driver
# ==== hash ====
client["restaurants"].find({ "address.building": "1007" })
# ==== array ====
client["restaurants"].find({ "address.coord.0": -73.856077 })
# == array size ==
client["restaurants"].find({ "address.coord" => {"$size" => 2} })
# = array & hash =
client["restaurants"].find({ grades: {"$elemMatch" => {grade: "C", score: 10}} })
mongoid
# ==== hash ====
Restaurant.where({ "address.building": "1007" })
# ==== array ====
Restaurant.where({ "address.coord.0": -73.856077 })
# == array size ==
Restaurant.where({ "address.coord" => {"$size" => 2} })
# = array & hash =
Restaurant.where({ grades: {"$elemMatch" => {grade: "C", score: 10}} })
Update Operation
mongo shell
// == update one ==
db.restaurants.update(
{ _id: ObjectId('58f4cad08fb238094bd9f518') },
{ $set: {name: 'keegoo'} }
)
// = update multi =
db.restaurants.update(
{ name: 'my restaurants 01' },
{ $set: {name: 'my restaurants 02'} },
{ multi: true }
)
// = create if not find =
db.restaurants.update(
{ name: 'a wierd name' },
{ $set: {name: 'my restaurants 01', borough: 'Shenzhen'} },
{ upsert: true }
)
// ==== array ====
db.restaurants.update(
{ _id: ObjectId('58f4baa603bb14a0c4306289') },
{ $set: {'address.coord.0': 25.0000} }
)
db.restaurants.update(
{
_id: ObjectId('58f4baa603bb14a0c4304e19'),
// 'address.coord': -58 match a position in 'coord' array.
'address.coord': -58
},
// '$' in 'address.coord.$' is this position.
{ $set: {'address.coord.$': 21} }
)
// ==== hash ====
db.restaurants.update(
{ _id: ObjectId("58f4baa603bb14a0c4306289") },
{ $set: {'address.zipcode': '438620'} }
)
// = array & hash =
db.restaurants.update(
{
_id: ObjectId('58f4baa603bb14a0c4304e19'),
// 'grades.date': ISODate('2014-09-06T00:00:00Z') match a position in 'grades' array.
'grades.date': ISODate('2014-09-06T00:00:00Z')
},
// '$' in 'grades.$.grade' is this position.
{ $set: {"grades.$.grade": 'B'} }
)
ruby mongo driver
# == update one ==
client["restaurants"].update_one(
{ _id: BSON::ObjectId("58f4cad08fb238094bd9f518") },
{ "$set": {name: "my restaurants 02"} }
)
# = update multi =
client["restaurants"].update_many(
{ name: "my restaurants 02" },
{ "$set": {name: "my restaurants 01"} }
)
# = create if not find =
client["restaurants"].update_one(
{ name: "a wierd name" },
{ "$set": {name: "my restaurants 01", borough: "Shenzhen"} },
{ upsert: true }
)
# ==== array ====
client["restaurants"].update_one(
{ _id: BSON::ObjectId("58f4baa603bb14a0c4306289") },
{ "$set": {"address.coord.0": 25.0000} }
)
client["restaurants"].update_one(
{
_id: BSON::ObjectId("58f4baa603bb14a0c4304e19"),
# "address.coord": -58 match a position in "coord" array.
"address.coord": -58
},
# "$" in "address.coord.$" is this position.
{ "$set": {"address.coord.$": 21} }
)
# ==== hash ====
client["restaurants"].update_one(
{ _id: BSON::ObjectId("58f4baa603bb14a0c4306289") },
{ "$set": {"address.zipcode": "438620"} }
)
# = array & hash =
client["restaurants"].update_one(
{
_id: BSON::ObjectId("58f4baa603bb14a0c4304e19"),
# "grades.date": ... match a position in "grades" array.
"grades.date": DateTime.strptime("2014-09-06", "%Y-%m-%d")
},
# "$" in "grades.$.grade" is this position.
{ "$set": {"grades.$.grade": "B"} }
)
mongoid
# == update one ==
Restaurant.find("58f4cad08fb238094bd9f518").update(name: "my restaurants 02")
# = update multi =
Restaurant.where({name: "my restaurants 01"}).update_all(name: "my restaurants 02")
# = create if not find =
# ...couldn't find a way...
# ==== array ====
Restaurant.where(_id: "58f4baa603bb14a0c4306289").set({"address.coord.0": 25.000})
Restaurant.where({
_id: "58f4baa603bb14a0c4304e19",
# "address.coord": -58 match a position in "coord" array.
'address.coord': -58
}).set("address.coord.$": 21)
# ==== hash ====
Restaurant.where(_id: "58f4baa603bb14a0c4306289").set({"address.zipcode": "438620"})
# = array & hash =
Restaurant.where({
_id: BSON::ObjectId("58f4baa603bb14a0c4304e19"),
# "grades.date": ... match a position in "grades" array.
"grades.date": DateTime.strptime("2014-09-06", "%Y-%m-%d")
}).set("grades.$.grade": "B")
Field Update Operation
mongo shell
// ===== inc =====
db.restaurants.update(
{ _id: ObjectId("58f4baa603bb14a0c4304e19") },
{ $inc: {"address.coord.0": 10, "address.coord.1": -20 } }
)
// ===== mul =====
db.restaurants.update(
{ _id: ObjectId("58f4baa603bb14a0c4304e19") },
{ $mul: { "address.coord.0": 1.10, "address.coord.1": 2 }}
)
// ==== rename ====
ruby mongo driver
# ===== inc =====
client["restaurants"].update_one(
{ _id: BSON::ObjectId("58f4baa603bb14a0c4304e19") },
{ "$inc": {"address.coord.0": 10, "address.coord.1": -20} }
)
# ===== mul =====
client["restaurants"].update_one(
{ _id: BSON::ObjectId("58f4baa603bb14a0c4304e19") },
{ "$mul": {"address.coord.0": 1.10, "address.coord.1": 2} }
)
mongoid
# ===== inc =====
Restaurant.find("58f4baa603bb14a0c4304e19").inc({"address.coord.0" => 10, "address.coord.1" => -20})
# ===== mul =====
# couldn't find any ...
Limit Fields
Return specific fields from the query instead of the whole document.
mongo shell
// === single ===
db.restaurants.find({borough: 'Bronx', cuisine: 'Bakery'}, {name: 1})
// === multiple ===
db.restaurants.find({borough: 'Bronx', cuisine: 'Bakery'}, {name: 1, restaurant_id: 1})
ruby mongo driver
# === single ===
client['restaurants'].find({borough: "Bronx", cuisine: "Bakery"}).projection({name: 1})
# or
client['restaurants'].find({borough: "Bronx", cuisine: "Bakery"}, projection: {name: 1})
# === multiple ===
client['restaurants'].find({borough: "Bronx", cuisine: "Bakery"}).projection({name: 1, restaurant_id: 1})
# or
client['restaurants'].find({borough: "Bronx", cuisine: "Bakery"}, projection: {name: 1, restaurant_id: 1})
mongoid
# ...
# omit class definition
# === single ===
Restaurant.where({borough: "Bronx", cuisine: "Bakery"}).only(:name)
# === multiple ===
Restaurant.where({borough: "Bronx", cuisine: "Bakery"}).only(:name, :restaurant_id)
Distinct
The values returned by distinct is an array.
mongo shell
// == collection ==
db.restaurants.distinct('name')
// == on query ===
db.restaurants.distinct('name', {borough: 'Bronx', cuisine: 'Bakery'})
ruby mongo driver
# == collection ==
client["restaurants"].find().distinct("name")
# == on query ===
client["restaurants"].find({borough: "Bronx", cuisine: "Bakery"}).distinct("name")
mongoid
# == collection ==
Restaurant.where({}).distinct("name")
# == on query ===
Restaurant.where({borough: "Bronx", cuisine: "Bakery"}).distinct("name")
Sort
mongo shell
db.schedulers.find().sort({'schedule.time': -1})
ruby mongo driver
mongoid
Scheduler.order_by("schedule.time" => :desc).limit(30)
Others
atomic writes
In MongoDB, a write operation is atomic on the level of a single document, even if the operation modifies multiple embedded documents within a single document.
Date
// ==== date ====
db.restaurants.find({ 'grades.0.date': new Date(1393804800000) })
// new Date(1393804800000) matched { "$date": 1393804800000 }