Build your first MongoDB App in Ruby @ StrangeLoop 2013

An updated version of the very popular workshop I gave at OSCON last year.

This presentation was given at Strangeloop 2013 as a 3 hour interactive workshop.

This tutorial will introduce the features of MongoDB by building a simple location-based application using MongoDB. The tutorial will cover the basics of MongoDB’s document model, query language, map-reduce framework and deployment architecture.

The tutorial will be divided into 5 sections:

  • Data modeling with MongoDB: documents, collections and databases
  • Querying your data: simple queries, geospatial queries, and text-searching
  • Writes and updates: using MongoDB’s atomic update modifiers
  • Trending and analytics: Using mapreduce and MongoDB’s aggregation framework
  • Deploying the sample application

Besides the knowledge to start building their own applications with MongoDB, attendees will finish the session with a working application they use to check into locations around Portland from any HTML5 enabled phone!

TUTORIAL PREREQUISITES
Each attendee should have a running version of MongoDB. Preferably the latest stable release 2.4.6. You can dowload MongoDB at http://www.mongodb.org/downloads.

Instructions for installing MongoDB are at http://docs.mongodb.org/manual/installation/.

Additionally we will be building an app in Ruby. Ruby 1.9.3+ is required for this. The current latest version of ruby is 2.0.0-p247.

There are many ways to install ruby, feel free to use your own solution if you have a preference. The following resources are also available:

We will be using the following GEMs and they MUST BE installed ahead of time so you can be ahead of the game and safe in the event that the Internet isn’t accommodating.

  • bson
  • bson_ext
  • haml
  • mongo
  • rack
  • rack-protection
  • rack shotgun
  • sinatra
  • tilt

Prior ruby experience is NOT required for this, though you should be familiar with basic web programming in a similar language (javascript, php, perl, python, etc).

We will NOT be using rails for this app.

Transcript

  1. Building your first MongoDB Application
  2. Agenda Introduction to MongoDB MongoDB Fundamentals Running MongoDB Schema Design Ruby & Sinatra Crash Course Building Our First App
  3. @spf13 Steve Francia AKA Chief Developer Advocate @ responsible for drivers, integrations, web & docs
  4. Introduction to mongodb
  5. What Is MongoDB?
    • Document * Open source * High performance * Horizontally scalable * Full featured MongoDB is a ___________ database
    • 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 Document Database
    • MongoDB is an open source project * On GitHub * Licensed under the AGPL * Commercial licenses available * Contributions welcome Open Source
    • 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 Performance
  6. Horizontally Scalable
    • Ad Hoc queries * Real time aggregation * Rich query capabilities * Traditionally consistent * Geospatial features * Support for most programming languages * Flexible schema Full Featured
  7. Depth of Functionality Scalability&Performance Memcached MongoDB RDBMS Database landscape
  8. http://www.mongodb.org/downloads
  9. What is a Record?
  10. Key → Value * One-dimensional storage * Single value is a blob * Query on key only * No schema * Value cannot be updated, only replaced Key Blob
  11. Relational * 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
  12. Document * 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
  13. Running Mongodb
  14. MongoD
  15. Mongo Shell
  16. user = { username: ‘fred.jones’, first_name: ‘fred’, last_name: ‘jones’, } Start with an object (or array, hash, dict, etc)
  17. db.users.insert(user) Insert the record No collection creation needed

  18. db.users.findOne() { “_id” : ObjectId(“50804d0bd94ccab2da652599”), “username” : “fred.jones”, “first_name” : “fred”, “last_name” : “jones” } Querying for the user

    • _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
    • ObjectId is a special 12 byte value * Guaranteed to be unique across your cluster * ObjectId(“50804d0bd94ccab2da652599”) |————-||———||—–||———-| ts mac pid inc ObjectId
  19. Schema Design
  20. Tradational schema design Focuses on data storage
  21. Document schema design Focuses on use
  22. 4 Building blocks of Document Design
  23. flexibility * 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
  24. Arrays * 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
  25. embedded Documents * An acceptable value is a document * Nested documents provide structure * Query any field at any level * Can be indexed
    • Object in your model * Associations with other entities entity Association 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
  26. Exercise 1: Model a business card
  27. Business Card
  28. Contacts { “_id”: 2, “name”: “Steven Jobs”, “title”: “VP, New Product Development”, “company”: “Apple Computer”, “phone”: “408-996-1010”, “address_id”: 1 } Referencing Addresses { “_id”: 1, “street”: “10260 Bandley Dr”, “city”: “Cupertino”, “state”: “CA”, “zip_code”: ”95014”, “country”: “USA” }
  29. Contacts { “_id”: 2, “name”: “Steven Jobs”, “title”: “VP, New Product Development”, “company”: “Apple Computer”, “address”: { “street”: “10260 Bandley Dr”, “city”: “Cupertino”, “state”: “CA”, “zip_code”: ”95014”, “country”: “USA” }, “phone”: “408-996-1010” } embedding
  30. Exercise 2: Store a business card
  31. Contacts db.contacts.insert( { “_id”: 2, “name”: “Steven Jobs”, “title”: “VP, New Product Development”, “company”: “Apple Computer”, “phone”: “408-996-1010”, “address_id”: 1 } ) Inserting with Reference Addresses db.addresses.insert( { “_id”: 1, “street”: “10260 Bandley Dr”, “city”: “Cupertino”, “state”: “CA”, “zip_code”: ”95014”, “country”: “USA” } )
  32. Exercise 3: Retrieve a business card
  33. Contacts c = db.contacts.findOne( { “name”: “Steven Jobs”, } ) Querying with Reference Addresses db.addresses.findOne( { “_id”: c.address_id, “street”: “10260 Bandley Dr”, “city”: “Cupertino”, “state”: “CA”, “zip_code”: ”95014”, “country”: “USA” } )
  34. Building a MongoDB Application
  35. MongoDB has native bindings for nearly all languages
  36. 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 drivers
  37. Building an app 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
  38. Ruby Crash Course
  39. everything is an object 1.class ‘a’.class :z.class class Foo end Foo.class Foo.new.class # => Fixnum # => String # => Symbol # => Class # => Foo
  40. Structure Method Class Invocation def do_stuff(thing) thing.do_the_stuff end class TheThing def do_the_stuff puts “Stuff was done!” end end do_stuff(TheThing.new)
  41. Strings name = ‘World’ # => “World” “Hello, #{name}” # => “Hello, World” ‘Hello, #{name}’ # => “Hello, #{name}”
  42. Numbers 1 + 1 # => 2 1 + 1.1 # => 2.1 6 * 7 # => 42 6 ** 7 # => 279936 Math.sqrt(65536) # => 256.0 1.class # => Fixnum (2 ** 42).class # => Fixnum (2 ** 64).class # => Bignum 1.1.class # => Float
  43. Arrays Array.new Array.new(3) [] a = [1,2,3] a[0] = ‘one’ a a[-1] a[1..2] # => [] # => [nil, nil, nil] # => [] # => [1, 2, 3] # => “one” # => [“one”, 2, 3] # => 3 # => [2, 3]
  44. Hashes Hash.new {} h = {1 => “one”, 2 => “two”} h[1] h[“1”] h[:one] = “einz” h[:one] h.keys h.values # => {} # => {} # => “one” # => nil # => “einz” # => “einz” # => [1, 2, :one] # => [“one”, “two”, “einz”]
  45. Variables & Names CamelCased # Classes, modules with_underscores # methods, local variables @instance_variable @@class_variable $GLOBAL_VARIABLE
  46. Control Structures if condition # … elsif other_condition # … end unless condition # … end while # … end
  47. Sinatra is… not Rails not a framework a DSL for quickly creating web applications in Ruby with minimal effort
  48. Hello World # myapp.rb require ‘sinatra’ get ‘/’ do ‘Hello world!’ end
  49. HTTP Actions 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
  50. Routes 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
  51. Splat 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
  52. Building our App in Ruby
  53. Introducing the milieu app
  54. pre-populating our database
  55. Download & Import the venues curl -L http://j.mp/StrangeLoopVenues | mongoimport -d milieu -c venues wget http://c.spf13.com/dl/StrangeLoopVenues.json mongoimport -d milieu -c venues StrangeLoopVenues.json Database Collection
  56. Mongo Shell
  57. Let’s look at the venues > use milieu switched to db milieu > db.venues.count() 50 Database
  58. Let’s look at the venues > db.venues.findOne() { “_id” : ObjectId(“52335163695c9d31c2000001”), “location” : { “address” : “1820 Market St”, “distance” : 85, “postalCode” : “63103”, “city” : “Saint Louis”, “state” : “MO”, “country” : “United States”, “cc” : “US”, “geo” : [ -90.20761747801353, 38.62893438211461 ] }, “name” : “St. Louis Union Station Hotel- A DoubleTree by Hilton”, “contact” : { “phone” : “3146215262”, “formattedPhone” : “(314) 621-5262”, “url” : “http://www.stlunionstationhotel.com” }, “stats” : { “checkinsCount” : 0, “usersCount” : 0 } }
  59. Creating a Geo index > db.venues.ensureIndex({ ‘location.geo’ : ‘2d’}) > db.venues.getIndexes() [ { “v” : 1, “key” : { “id” : 1 }, “ns” : “milieu.venues”, “name” : “id” }, { “v” : 1, “key” : { “location.geo” : “2d” }, “ns” : “milieu.venues”, “name” : “location.geo” } ]
  60. Skeleton
  61. Start with a skeleton /Users/steve/Code/milieu/app/ ▸ config/ ▸ helpers/ ▾ model/ mongodb.rb mongoModule.rb user.rb ▾ public/ ▸ bootstrap/ ▾ css/ styles.css ▸ images/ ▾ views/ footer.haml index.haml layout.haml login.haml navbar.haml register.haml user_dashboard.haml user_profile.haml venue.haml venues.haml app.rb config.ru Gemfile Rakefile README
  62. 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.
  63. Run app 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
  64. Open Browser to localhost:9393
  65. Preview — error Screen
  66. listing Venues
  67. Connecting to MongoDB require ‘mongo’ require ‘./model/mongoModule’ require ‘./model/user’ # Connection code goes here CONNECTION = Mongo::Connection.new(“localhost”) DB = CONNECTION.db(‘milieu’) # Alias to collections goes here USERS = DB[‘users’] VENUES = DB[‘venues’] CHECKINS = DB[‘checkins’] model/mongodb.rb
  68. listing Venues get ‘/venues’ do # Code to list all venues goes here @venues = VENUES.░░░░░ haml :venues end app.rb
  69. listing Venues get ‘/venues’ do # Code to list all venues goes here @venues = VENUES.find haml :venues end app.rb
  70. listing Venues .container .content %h2 Venues %table.table.table-striped %thead %tr %th Name %th Address %th Longitude %th Latitude %tbody ~@venues.each do |venue| %tr %td %a{:href => ‘/venue/’ « venue['_id'].to_s}= venue[‘name’] %td= venue[‘location’][‘address’] ? venue[‘location’][‘address’] : ‘ ’ %td= venue[‘location’][‘geo’][0].round(2) %td= venue[‘location’][‘geo’][1].round(2) views/venues.haml
  71. listing Venueslocalhost:9393/venues
  72. Paginating Venues get ‘/venues/?:page?’ do @page = params.fetch(‘page’, 1).to_i pp = 10 @venues = VENUES.find.░░░░░(░░░░).░░░░(░░░) @total_pages = (VENUES.░░░░░.to_i / pp).ceil haml :venues end app.rb # replaces the prior entry
  73. Paginating Venues get ‘/venues/?:page?’ do @page = params.fetch(‘page’, 1).to_i pp = 10 @venues = VENUES.find.skip((@page - 1) * pp).limit(pp) @total_pages = (VENUES.count.to_i / pp).ceil haml :venues end app.rb # replaces the prior entry
  74. .container .content %h2 Venues %table.table.table-striped %thead %tr %th Name %th Address %th Longitude %th Latitude %tbody ~@venues.each do |venue| %tr %td %a{:href => ‘/venue/’ « venue['_id'].to_s}= venue[‘name’] %td= venue[‘location’][‘address’] ? venue[‘location’][‘address’] : ‘ ’ %td= venue[‘location’][‘geo’][0].round(2) %td= venue[‘location’][‘geo’][1].round(2) =pager('/venues') listing Venues views/venues.haml
  75. paging through Venueslocalhost:9393/venues
  76. Creating Users
  77. Creating Users 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
  78. Creating Users 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
  79. Creating Users 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("
    ")) end flash(tmp) redirect ‘/create’ end end app.rb
  80. Logging in part 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
  81. Logging in part 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
  82. Logging in part 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
  83. User Dashboard .container .content .page-header -unless @suser == nil? %h2=“Dashboard” %br %image{src: “http://www.gravatar.com/avatar/" « @suser.email_hash.to_s « ‘.png’} %h3= @suser.name.to_s -else redirect ‘/’ %small %a{href: “/user/” « @suser.email.to_s « “/profile”} profile .container#main-topic-nav views/user_dashboard.haml
  84. Dashboard localhost:9393/dashboard
  85. Viewing Users
  86. Finding a user 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
  87. Finding a user 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
  88. Creating an INdex > db.users.ensureIndex({email : 1}) > db.users.getIndexes() [ { “v” : 1, “key” : { “_id” : 1 }, “ns” : “milieu.users”, “name” : “id” }, { “v” : 1, “key” : { “email” : 1 }, “ns” : “milieu.users”, “name” : “email_1” } ]
  89. A Venue
  90. Showing a Venue get ‘/venue/:_id’ do object_id = ░░░░░░░░░░░░░░ @venue = ░░░░░░░░░░( { ░░░░ => object_id }) haml :venue end app.rb
  91. Showing a Venue get ‘/venue/:_id’ do object_id = BSON::ObjectId.from_string(params[:_id]) @venue = VENUES.find_one( { :_id => object_id }) haml :venue end app.rb
  92. Showing a Venue .row .col-md-4 %h2= @venue[‘name’].to_s %p =@venue[‘location’][‘address’].to_s %br= @venue[‘location’][‘city’].to_s + ' ' + @venue[‘location’][‘state’].to_s + ' ' + @venue[‘location’][‘postalCode’].to_s .col-md-8 %image{:src => '' « gmap_url(@venue, {:height => 300, :width => 450}) } views/venue.haml
  93. A Venue localhost:9393/venue/{id}
  94. Nearby VenueS
  95. Nearby Venues get ‘/venue/:_id’ do object_id = BSON::ObjectId.from_string(params[:_id]) @venue = VENUES.find_one({ :_id => object_id }) @nearby_venues = ░░░░░.░░░░░( {░░░░░ =>{░░░░░=>[ ░░░░░,░░░░░]}} ).░░░░░(4).░░░░░(1) haml :venue end app.rb
  96. Nearby Venues get ‘/venue/:_id’ do object_id = BSON::ObjectId.from_string(params[:_id]) @venue = VENUES.find_one({ :_id => object_id }) @nearby_venues = VENUES.find( { :‘location.geo’ => { ░░░░░ => [ ░░░░░,░░░░░] } }).░░░░░(4).░░░░░(1) haml :venue end app.rb
  97. Nearby Venues get ‘/venue/:_id’ do object_id = BSON::ObjectId.from_string(params[:_id]) @venue = VENUES.find_one({ :_id => object_id }) @nearby_venues = VENUES.find( { :‘location.geo’ => { ░░░░░ => [ ░░░░░,░░░░░] } }).limit(4).skip(1) haml :venue end app.rb
  98. Nearby Venues get ‘/venue/:_id’ do object_id = BSON::ObjectId.from_string(params[:_id]) @venue = VENUES.find_one({ :_id => object_id }) @nearby_venues = VENUES.find( { :‘location.geo’ => { :$near => [ @venue[‘location’][‘geo’][0], @venue[‘location’][‘geo’][1]] } }).limit(4).skip(1) haml :venue end app.rb
  99. … .row - @nearby_venues.each do |nearby| .col-md-3 %h2 %a{:href => ‘/venue/’ + nearby['_id'].to_s}= nearby[‘name’].to_s %p =nearby[‘location’][‘address’].to_s %br= nearby[‘location’][‘city’].to_s + ' ' + nearby[‘location’][‘state’].to_s + ' ' + nearby[‘location’][‘postalCode’].to_s %a{:href => ‘/venue/’ + nearby['_id'].to_s} %image{:src => '' « gmap_url(nearby, {:height => 150, :width => 150, :zoom => 17}) } views/venue.haml listing Nearby Venues
  100. Nearby Venues localhost:9393/venue/{id}
  101. Checking IN
  102. Checking 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
  103. Checking 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
  104. Checking 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
  105. Checking 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
  106. Checking 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
  107. You’ve been here 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 login to join them.’ end end helpers/milieu.rb
  108. Checking 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
  109. A Venue localhost:9393/venue/{id}
  110. What we’ve learned * 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
  111. Next ?
  112. It’s on Github
  113. Some Ideas * Create interface to add venues * Connect to foursquare * Login w/twitter * Badges or Categories * Enable searching of venues * Tips / Reviews
  114. Tweet if you liked it! Questions? http://spf13.com http://github.com/spf13 @spf13