This document provides an agenda and introduction for building a first MongoDB application. It discusses MongoDB fundamentals like documents, schemas, and queries. It then walks through setting up a Sinatra application scaffolded with folders, connecting it to a MongoDB database, and pre-populating the database with venue data. The document demonstrates basic CRUD operations in the MongoDB shell and indexes the venue location data geographically. Finally, it discusses starting the Sinatra server and opening the initial application in the browser.
6. * Document
* Open source
* High performance
* Horizontally scalable
* Full featured
MongoDB is a ___________ database
7. * Not for .PDF & .DOC files
* A document is essentially an associative
array
* Document == JSON object
* Document == PHP Array
* Document == Python Dict
* Document == Ruby Hash
* etc
Documt Database
8. * MongoDB is an open source
project
* On GitHub
* Licensed under the AGPL
* Commercial licenses available
* Contributions welcome
Op Source
9. * Written in C++
* Extensive use of memory-mapped files
i.e. read-through write-through memory
caching.
* Runs nearly everywhere
* Data serialized as BSON (fast parsing)
* Full support for primary & secondary indexes
* Document model = less work
High Pfoance
11. * Ad Hoc queries
* Real time aggregation
* Rich query capabilities
* Traditionally consistent
* Geospatial features
* Support for most programming languages
* Flexible schema
Full Ftured
15. K → Value
* One-dimensional storage
* Single value is a blob
* Query on key only
* No schema
* Value cannot be updated, only
replaced
Key Blob
16. Raonal
* Two-dimensional storage
(tuples)
* Each field contains a single
value
* Query on any field
* Very structured schema (table)
* In-place updates
* Normalization process requires
many tables, joins, indexes, and
poor data locality
Primary
Key
17. Documt
* N-dimensional storage
* Each field can contain 0, 1,
many, or embedded values
* Query on any field & level
* Flexible schema
* Inline updates *
* Embedding related data has optimal data
locality, requires fewer indexes, has better
performance
_id
24. * _id is the primary key in MongoDB
* Automatically indexed
* Automatically created as an
ObjectId if not provided
* Any unique immutable value could
be used
_id
25. * ObjectId is a special 12 byte
value
* Guaranteed to be unique across
your cluster
* ObjectId("50804d0bd94ccab2da652599")
|-------------||---------||-----||----------|
ts mac pid inc
ObjectId
30. exibi
* Choices for schema design
* Each record can have different fields
* Field names consistent for
programming
* Common structure can be enforced by
application
* Easy to evolve as needed
31. Ays
* Each field can be:
* Absent
* Set to null
* Set to a single value
* Set to an array of many values
* Query for any matching value
* Can be indexed and each value in the array
is in the index
32. beed Documts
* An acceptable value is a
document
* Nested documents provide
structure
* Query any field at any level
* Can be indexed
33. * Object in your model
* Associations with other entities
Aßociaon
Referencing (Relational) Embedding (Document)
has_one embeds_one
belongs_to embedded_in
has_many embeds_many
has_and_belongs_to_many
MongoDB has both referencing and embedding for universal coverage
44. Official Support for 12 languages
Community drivers for tons more
Drivers connect to mongo servers
Drivers translate BSON into native types
mongo shell is not a driver, but works like
one in some ways
Installed using typical means (npm, pecl,
gem, pip)
MongoDB dvs
45.
46.
47. Building an a in Ruby?
Had to pick a language
Sinatra is very minimal and approachable
Wanted to focus on MongoDB interaction
Ruby gems are awesome
Works well on Windows, OS X & Linux
Seemed like a good idea at the time
60. TP Acons
In Sinatra, a route is an HTTP method paired with a URL-matching
pattern.
Each route is associated with a block:
get '/' do
.. show something ..
end
post '/' do
.. create something ..
end
put '/' do
.. replace something ..
end
delete '/' do
.. annihilate something ..
end
61. Roes
Routes are matched in the order they are defined.
The first route that matches the request is invoked.
Route patterns may include named parameters,
accessible via the params hash:
get '/hello/:name' do
# matches "GET /hello/foo" and "GET /hello/bar"
# params[:name] is 'foo' or 'bar'
"Hello #{params[:name]}!"
end
#You can also access named parameters via block parameters:
get '/hello/:name' do |n|
"Hello #{n}!"
end
62. Spt
Route patterns may also include splat
(or wildcard) parameters, accessible via the
params[:splat] array:
get '/say/*/to/*' do
# matches /say/hello/to/world
params[:splat] # => ["hello", "world"]
end
get '/download/*.*' do
# matches /download/path/to/file.xml
params[:splat] # => ["path/to/file", "xml"]
end
73. Download & Install deps
mkdir milieu
cd milieu
wget http://c.spf13.com/dl/GettingStarted.tgz
tar zxvf GettingStarted.tgz
bundle install
Resolving dependencies...
Using bson (1.9.2)
Using bson_ext (1.9.2)
Using googlestaticmap (1.1.4)
Using tilt (1.4.1)
Using haml (4.0.3)
Using mongo (1.9.2)
Using rack (1.5.2)
Using rack-protection (1.5.0)
Using shotgun (0.9)
Using sinatra (1.4.3)
Using bundler (1.3.5)
Your bundle is complete! Use `bundle show [gemname]` to see where a bundled gem is
installed.
74. Run a
shotgun
== Shotgun/WEBrick on http://127.0.0.1:9393/
[2013-09-13 21:25:43] INFO WEBrick 1.3.1
[2013-09-13 21:25:43] INFO ruby 2.0.0 (2013-06-27)
[x86_64-darwin12.3.0]
[2013-09-13 21:25:43] INFO WEBrick::HTTPServer#start:
pid=85344 port=9393
88. Crng Uss
Users are a bit special in our app
Not just data
Special considerations for secure
password handling
Not complicated on MongoDB side, but
slightly complicated on Ruby side
89. Crng Uss
class User
attr_accessor :_id, :name, :email, :email_hash, :salt, :hashed_password,
:collection, :updated_at
def init_collection
self.collection = 'users'
end
def password=(pass)
self.salt = random_string(10) unless self.salt
self.hashed_password = User.encrypt(pass, self.salt)
end
def save
col = DB[self.collection]
self.updated_at = Time.now
col.save(self.to_hash)
end
end
model/user.rb
Inherited from
MongoModule.rb
90. Crng Uss
post '/register' do
u = User.new
u.email = params[:email]
u.password = params[:password]
u.name = params[:name]
if u.save()
flash("User created")
session[:user] = User.auth( params["email"], params["password"])
redirect '/user/' << session[:user].email.to_s << "/dashboard"
else
tmp = []
u.errors.each do |e|
tmp << (e.join("<br/>"))
end
flash(tmp)
redirect '/create'
end
end
app.rb
91. Loing in pa 1
configure do
enable :sessions
end
before do
unless session[:user] == nil
@suser = session[:user]
end
end
get '/user/:email/dashboard' do
haml :user_dashboard
end
app.rb
92. Loing in pa 2
post '/login' do
if session[:user] = User.auth(params["email"], params["password"])
flash("Login successful")
redirect "/user/" << session[:user].email << "/dashboard"
else
flash("Login failed - Try again")
redirect '/login'
end
end
app.rb
93. Loing in pa 3
def self.auth(email, pass)
u = USERS.find_one("email" => email.downcase)
return nil if u.nil?
return User.new(u) if User.encrypt(
pass, u['salt']) == u['hashed_password']
nil
end
user.rb
97. nding a us
get '/user/:email/profile' do
u = USERS.░░░░░(
░░░░░ => ░░░░░.░░░░░)
if u == nil
return haml :profile_missing
else
@user = User.new(u)
end
haml :user_profile
end
app.rb
98. nding a us
get '/user/:email/profile' do
u = USERS.find_one(
"email" => params[:email].downcase)
if u == nil
return haml :profile_missing
else
@user = User.new(u)
end
haml :user_profile
end
app.rb
113. Checng in
get '/venue/:_id/checkin' do
object_id = BSON::ObjectId.from_string(params[:_id])
@venue = VENUES.find_one({ :_id => object_id })
user = USERS. ░░░░░_and_░░░░░(░░░░░)
if ░░░░░
░░░░░ ░░░░░ ░░░░░ ░░░░░
else
░░░░░ ░░░░░ ░░░░░ ░░░░░
end
flash('Thanks for checking in')
redirect '/venue/' + params[:_id]
end
app.rb
114. Checng in
get '/venue/:_id/checkin' do
object_id = BSON::ObjectId.from_string(params[:_id])
@venue = VENUES.find_one({ :_id => object_id })
user = USERS.find_and_modify(
:query => ░░░░░,
:update => ░░░░░,
:new => 1)
if ░░░░░
░░░░░ ░░░░░ ░░░░░ ░░░░░
else
░░░░░ ░░░░░ ░░░░░ ░░░░░
end
flash('Thanks for checking in')
redirect '/venue/' + params[:_id]
end
app.rb
115. Checng in
get '/venue/:_id/checkin' do
object_id = BSON::ObjectId.from_string(params[:_id])
@venue = VENUES.find_one({ :_id => object_id })
user = USERS.find_and_modify(
:query => { :_id => @suser._id},
:update => {:$inc =>{ "venues." << object_id.to_s => 1 } },
:new => 1)
if ░░░░░
░░░░░ ░░░░░ ░░░░░ ░░░░░
else
░░░░░ ░░░░░ ░░░░░ ░░░░░
end
flash('Thanks for checking in')
redirect '/venue/' + params[:_id]
end
app.rb
116. Checng in
get '/venue/:_id/checkin' do
object_id = BSON::ObjectId.from_string(params[:_id])
@venue = VENUES.find_one({ :_id => object_id })
user = USERS.find_and_modify(:query => { :_id => @suser._id},
:update => {:$inc =>
{ "venues." << object_id.to_s => 1 } }, :new => 1)
if user['venues'][params[:_id]] == 1
VENUES.update(░░░░░)
else
VENUES.update(░░░░░)
end
flash('Thanks for checking in')
redirect '/venue/' + params[:_id]
end
app.rb
117. Checng in
get '/venue/:_id/checkin' do
object_id = BSON::ObjectId.from_string(params[:_id])
@venue = VENUES.find_one({ :_id => object_id })
user = USERS.find_and_modify(:query => { :_id => @suser._id},
:update => {:$inc =>
{ "venues." << object_id.to_s => 1 } }, :new => 1)
if user['venues'][params[:_id]] == 1
VENUES.update({ :_id => @venue['_id']}, { :$inc =>
{ :'stats.usersCount' => 1, :'stats.checkinsCount' => 1}})
else
VENUES.update({ _id: @venue['_id']},
{ :$inc => { :'stats.checkinsCount' => 1}})
end
flash('Thanks for checking in')
redirect '/venue/' + params[:_id]
end
app.rb
118. You’ve be he
def user_times_at
if logged_in?
times = 'You have checked in here '
if !@suser.venues.nil? &&
!@suser.venues[params[:_id]].nil? times <<
@suser.venues[params[:_id]].to_s
else
times << '0'
end
times << ' times'
else
times = 'Please <a href='/login'>login</a> to
join them.'
end
end
helpers/milieu.rb
119. Checn In
%p
%a.btn.btn-primary.btn-large{:href =>
'/venue/' + @venue['_id'].to_s + '/checkin'}
Check In Here
%p
=@venue['stats']['usersCount'].ceil.to_s +
' users have checked in here ' +
@venue['stats']['checkinsCount'].ceil.to_s +
' times'
%p=user_times_at
views/venue.haml
121. What we’ve led
* Model data for
MongoDB
* Use MongoDB tools
to import data
* Create records from
shell & ruby
* Update records
* Atomic updates
* Create an index
* Create a geo index
* Query for data by
matching
* GeoQueries
* Pagination
* Single Document
Transactions
* Some ruby, sinatra,
haml, etc