practical ruby projects with mongodb - ruby kaigi 2010

128
PRACTICAL PROJECTS WITH

Upload: alex-sharp

Post on 15-May-2015

2.459 views

Category:

Technology


1 download

TRANSCRIPT

Page 1: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

PRACTICAL

PROJECTS WITH

Page 2: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

WHO AM I?

Alex SharpLead Developer at OptimisDev

@ajsharp (on the twitters)

alexjsharp.com (on the ‘tubes)

github.com/ajsharp (on the ‘hubs)

Page 3: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MAJOR POINTS

• Making the case for Mongo

• Brief intro to MongoDB

• Practical Projects

Page 4: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MAKING THE CASE

Page 5: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

THE PROBLEMS OF SQL

Page 6: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

A BRIEF HISTORY OF SQL

• Developed at IBM in early 1970’s.

• Designed to manipulate and retrieve data stored in relational databases

Page 7: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

A BRIEF HISTORY OF SQL

• Developed at IBM in early 1970’s.

• Designed to manipulate and retrieve data stored in relational databases

this is a problem

Page 8: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

WE DON’T WORK WITH DATA...

Page 9: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

WE WORK WITH OBJECTS

Page 10: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

WE DON’T CARE ABOUT

STORING DATA

Page 11: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

WE CARE ABOUT PERSISTING STATE

Page 12: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

DATA != OBJECTS

Page 13: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

THEREFORE...

Page 14: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

BOLD STATEMENT FORTHCOMING...

Page 15: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

relational DBs are an antiquated tool for our

needs

Page 16: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

OK, SO WHAT?

Relational schemas are designed to store and query data,

not persist objects.

Page 17: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

To reconcile this mismatch, we have

ORM’s

Page 18: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

Object Relational Mappers

Page 19: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

ORMs enable us to create a mapping between our

native object model

and a relational schema

Page 20: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

We’re forced to create

relationships for data

when what we really want is

properties for objects.

Page 21: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

THIS IS NOT EFFICIENT

Page 22: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

A WEIRD EXAMPLE...

Page 23: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

@alex = Person.new( :name => "alex", :stalkings => [ Friend.new("Jim"), Friend.new("Bob")])

Page 24: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

<Person:0x10017d030 @name="alex", @stalkings= [#<Friend:0x10017d0a8 @name="Jim">, #<Friend:0x10017d058 @name="Bob">]>

NATIVE RUBY OBJECT

Page 25: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

@alex.to_json=> { name: "alex", stalkings: [{ name: "Jim" }, { name: "Bob" }] }

JSON REPRESENTATION

Page 26: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

Relational Schema Representation

people: - name

stalkings: - name - stalker_id - stalkee_id

Page 27: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

What!?

SQL Schema Representationpeople: - name

stalkings: - name - stalker_id - stalkee_id

Page 28: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

Ruby -> JSON -> SQL

@alex.to_json{ name: "alex", stalkings: [{ name: "Jim" }, { name: "Bob" }] }

<Person:0x10017d030 @name="alex", @stalkings= [#<Friend:0x10017d0a8 @name="Jim">, #<Friend:0x10017d058 @name="Bob">]>

Ruby

JSON

SQLpeople: - name

stalkings: - name - stalker_id - stalkee_id

Page 29: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

Ruby -> JSON -> SQL

@alex.to_json{ name: "alex", stalkings: [{ name: "Jim" }, { name: "Bob" }] }

<Person:0x10017d030 @name="alex", @stalkings= [#<Friend:0x10017d0a8 @name="Jim">, #<Friend:0x10017d058 @name="Bob">]>

Ruby

JSON

SQLFeels like we’re working

too hard here

people: - name

stalkings: - name - stalker_id - stalkee_id

Page 30: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

You’re probably thinking...

“SQL isn’t that bad”

Page 31: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

Maybe.

Page 32: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

But we can do better.

Page 33: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

THE IDEAL PERSISTENCE LAYER:

Page 34: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

THE IDEAL PERSISTENCE LAYER:

1. To persist objects in their native state

Page 35: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

THE IDEAL PERSISTENCE LAYER:

1. To persist objects in their native state

2. Does not interfere w/ application development

Page 36: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

THE IDEAL PERSISTENCE LAYER:

1. To persist objects in their native state

2. Does not interfere w/ application development

3. Provides features needed to build modern web apps

Page 37: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MONGO TO THE RESCUE!

Page 38: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

WHAT IS MONGODB?

mongodb is a high-performance, schema-less, scalable, document-

oriented database

Page 39: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

STRENGTHS

Page 40: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

SCHEMA-LESS

Page 41: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

SCHEMA-LESS• Great for rapid, iterative, agile development

Page 42: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

SCHEMA-LESS• Great for rapid, iterative, agile development

• Makes things possible that in an RDBMS are either

a. impossibly hard

b. virtually impossible

c. way harder than they should be

Page 43: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

DOCUMENT-ORIENTED

Page 44: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

DOCUMENT-ORIENTED

• Mongo stores documents, not rows

• documents are stored as binary json

Page 45: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

DOCUMENT-ORIENTED

• Mongo stores documents, not rows

• documents are stored as binary json

• Rich, familiar query syntax

Page 46: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

DOCUMENT-ORIENTED

• Mongo stores documents, not rows

• documents are stored as binary json

• Rich, familiar query syntax

• Embedded documents eliminate many modeling headaches

Page 47: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

sample mongo document

{'status': 500, 'request_method': 'post', 'controller_action': 'notes#create', 'user': {'first_name': 'John', 'last_name': 'Doe'}}

QUERY EXAMPLES

Page 48: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

logs.find('status' => 500)

# SQL equivalent:# select * from logs WHERE logs.status = 500

QUERY EXAMPLES

{'status': 500, 'request_method': 'post', 'controller_action': 'notes#create', 'user': {'first_name': 'John', 'last_name': 'Doe'}}

Page 49: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

logs.find('status' => 500, 'user.last_name' => 'Doe')

# SQL equivalent: none

QUERY EXAMPLES

{'status': 500, 'request_method': 'post', 'controller_action': 'notes#create', 'user': {'first_name': 'John', 'last_name': 'Doe'}}

Page 50: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

logs.find('status' => 500, 'user.last_name' => 'Doe')

QUERY EXAMPLES

user is an embedded document

{'status': 500, 'request_method': 'post', 'controller_action': 'notes#create', 'user': {'first_name': 'John', 'last_name': 'Doe'}}

Page 51: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

logs.find('status' => 500, 'user.last_name' => 'Doe')

QUERY EXAMPLES

in sql, we would have JOIN-ed to the ‘users’ table to do this query

Page 52: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

# also works w/ regexlogs.find('request_method' => /p/i)

# SQL equivalent:# select * from logs where request_method LIKE %p%

QUERY EXAMPLES

{'status': 500, 'request_method': 'post', 'controller_action': 'notes#create', 'user': {'first_name': 'John', 'last_name': 'Doe'}}

Page 53: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

BIG DATA

Page 54: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

BIG DATA

• Built to scale horizontally into the cloudz

Page 55: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

BIG DATA

• Built to scale horizontally into the cloudz

• Auto-sharding

• set a shard-key, a cluster of nodes, go

Page 56: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

FAST WRITES

Page 57: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

FAST WRITES

• In-place updates

• i.e. upsert

Page 58: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

FAST WRITES

• In-place updates

• i.e. upsert

• Lot’s of db commands for fast updates

• $set, $unset, $inc, $push

Page 59: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

UPSERT

• Update if present, insert otherwise

• careful: update does a hard replace of the entire document

# initial insertposts.save(:title => 'my post', :body => '...')

# update-in-place "upsert"posts.save(:_id => 'post_id', :title => 'new title', :body => '...')

Page 60: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

AGGREGATION

Map/reduce for aggregation

Page 61: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

// mapfunction() { emit(this.controller_action, { count: 1 });}

// reducefunction(key, values) { var sum = 0; values.forEach(function(doc) { sum += doc.count; }); return { count: sum };}

AGGREGATION

Page 62: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

FAST READS

Optimize reads with indexes, just like an rdbms

Page 63: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

# index is only created if it does not existlogs.create_index('status')

# can index on embedded documentslogs.create_index('user.first_name')

# create compound indexeslogs.create_index( [['user.first_name', 1], ['user.last_name', 1]])

Page 64: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

WEAKNESSES

Page 65: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

JOINS

nope.

Page 66: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MULTI-DOCUMENTTRANSACTIONS

no-sir-ee.

Page 67: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

RELATIONAL INTEGRITY

not a chance.

Page 68: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

PRACTICAL PROJECTS

Page 69: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

1. Accounting Application

2. Capped Collection Logging

3. Blogging app

Page 70: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

GENERAL LEDGER ACCOUNTING APPLICATION

Page 71: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

THE OBJECT MODELledger

transactions

entries

*

*

Page 72: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

THE OBJECT MODEL

# Credits Debits

1 { :account => “Cash”, :amount => 100.00 }

{ :account => “Notes Pay.”, :amount => 100.00 }

2 { :account => “A/R”, :amount => 25.00 }

{ :account => “Gross Revenue”, :amount => 25.00 }

Ledger Entries

{

Line item {

Line item

Page 73: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

THE OBJECT MODEL

Page 74: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

THE OBJECT MODEL

• Each ledger line item belongs to a ledger

Page 75: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

THE OBJECT MODEL

• Each ledger line item belongs to a ledger

• Each ledger line item has two (or more) ledger entries

• must have at least one credit and one debit

• credits and debits must balance

Page 76: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

THE OBJECT MODEL

• Each ledger line item belongs to a ledger

• Each ledger line item has two (or more) ledger entries

• must have at least one credit and one debit

• credits and debits must balance

• These objects must be transactional

Page 77: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

@debit = Entry.new(:account => 'Cash', :amount => 100.00, :type => 'credit')

@credit = Entry.new(:account => 'Notes Pay.', :amount => 100.00, :type => 'debit')

@line_item = LineItem.new(:ledger_id => 1, :entries => [@debit, @credit])

SQL-BASED OBJECT MODEL

Page 78: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

@debit = Entry.new(:account => 'Cash', :amount => 100.00, :type => 'credit')

@credit = Entry.new(:account => 'Notes Pay.', :amount => 100.00, :type => 'debit')

@line_item = LineItem.new :ledger_id => 1, :entries => [@debit, @credit]

In a relational schema, we need a database transaction to ensure multi-row atomicity

SQL-BASED OBJECT MODEL

Page 79: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

@debit = Entry.new(:account => 'Cash', :amount => 100.00, :type => 'credit')

@credit = Entry.new(:account => 'Notes Pay.', :amount => 100.00, :type => 'debit')

@line_item = LineItem.new :ledger_id => 1, :entries => [@debit, @credit]

Because we need to create 3 new records in the database here

SQL-BASED OBJECT MODEL

Page 80: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MONGO-FIED MODELING@line_item = LineItem.new(:ledger_id => 1, :entries => [ {:account => 'Cash', :type => 'debit', :amount => 100}, {:account => 'Notes pay.', :type => 'debit', :amount => 100} ])

Page 81: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MONGO-FIED MODELING

This is the perfect case for embedded documents.

@line_item = LineItem.new(:ledger_id => 1, :entries => [ {:account => 'Cash', :type => 'debit', :amount => 100}, {:account => 'Notes pay.', :type => 'debit', :amount => 100} ])

Page 82: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MONGO-FIED MODELING

We will never have more than a few entries (usually two)

@line_item = LineItem.new(:ledger_id => 1, :entries => [ {:account => 'Cash', :type => 'debit', :amount => 100}, {:account => 'Notes pay.', :type => 'debit', :amount => 100} ])

Page 83: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MONGO-FIED MODELING@line_item = LineItem.new(:ledger_id => 1, :entries => [ {:account => 'Cash', :type => 'debit', :amount => 100}, {:account => 'Notes pay.', :type => 'debit', :amount => 100} ])

Embedded docs are perfect for “contains” many relationships

Page 84: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MONGO-FIED MODELING

DB-level transactions no-longer needed.Mongo has single document atomicity.

@line_item = LineItem.new(:ledger_id => 1, :entries => [ {:account => 'Cash', :type => 'debit', :amount => 100}, {:account => 'Notes pay.', :type => 'debit', :amount => 100} ])

Page 85: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

WINS

Page 86: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

WINS

•Simplified, de-normalized data model

•Objects modeled differently in Mongo than SQL

Page 87: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

WINS

•Simplified, de-normalized data model

•Objects modeled differently in Mongo than SQL

•Simpler data model + no transactions = WIN

Page 88: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

LOGGING WITH CAPPED

COLLECTIONS

Page 89: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

BASELESS STATISTIC

95% of the time logs are NEVER used.

Page 90: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MOST LOGS ARE ONLY USED WHEN SOMETHING GOES

WRONG

Page 91: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

• Fixed-sized, limited operation, auto age-out collections (kinda like memcached, except persistent)

• Fixed insertion order

• Super fast (faster than normal writes)

• Ideal for logging and caching

CAPPED COLLECTIONS

Page 92: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

GREAT. WHAT’S THAT MEAN?

We can log additional pieces of arbitrary data effortlessly due to Mongo’s schemaless nature.

Page 93: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

THIS IS AWESOME

Page 94: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

CAPTURE.QUERY.

ANALYZE. PROFIT.

Page 95: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

ALSO A REALLY HANDY TROUBLESHOOTING

TOOL

Page 96: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

User-reported errors are difficult to diagnose

Page 97: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

Wouldn’t it be useful to see the complete click-path?

Page 98: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

BUNYAN

Thin ruby layer around a MongoDB capped collection

http://github.com/ajsharp/bunyan

Page 99: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

BUNYAN

Still needs a middleware api.

Want to contribute?

http://github.com/ajsharp/bunyan

Page 100: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

Text

require 'bunyan'

Bunyan::Logger.configure do database 'my_bunyan_db' collection "#{Rails.env}_bunyan_log" size 1073741824 # 1.gigabyteend

Page 101: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

SINATRA FRONT-END TO BUNYAN

PAPERMILLhttp://github.com/ajsharp/papermill

Page 102: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

DEMO TIME

PAPERMILLhttp://github.com/ajsharp/papermill

Page 103: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010
Page 104: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

BLOGGING APPLICATION

Page 105: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

BLOGGING APPLICATION

Much easier to model with Mongo than a relational database

Page 106: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

A post has an author

BLOGGING APPLICATION

Page 107: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

A post has an author

A post has many tags

BLOGGING APPLICATION

Page 108: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

A post has an author

A post has many tags

BLOGGING APPLICATION

A post has many comments

Page 109: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

A post has an author

A post has many tags

A post has many comments

Instead of joining separate tables,we can use embedded documents.

BLOGGING APPLICATION

Page 110: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MONGOMAPPER

Page 111: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MONGOMAPPER•MongoDB “ORM” developed by John Nunemaker

Page 112: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MONGOMAPPER•MongoDB “ORM” developed by John Nunemaker

•Very similar syntax to DataMapper

Page 113: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MONGOMAPPER•MongoDB “ORM” developed by John Nunemaker

•Very similar syntax to DataMapper

•Declarative rather than inheritance-based

Page 114: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MONGOMAPPER•MongoDB “ORM” developed by John Nunemaker

•Very similar syntax to DataMapper

•Declarative rather than inheritance-based

•Very easy to drop into rails

Page 115: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

require 'mongo'connection = Mongo::Connection.new.db("blog_app")

class Post include MongoMapper::Document belongs_to :author, :class_name => "User" key :title, String, :required => true key :body, String, :required => true key :author_id, Integer, :required => true key :published_at, Time key :published, Boolean, :default => false key :tags, Array, :default => [] timestamps!end

MONGOMAPPER

Page 116: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

INDEXES!

Post.collection.create_index('tags')

Post.collection.create_index('title')

Page 117: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

WINS

Page 118: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

WINS

Simpler persistence model matches simple object model

Page 119: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

NEXT STEPS

Page 120: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

OBJECT-DOCUMENT MAPPERS

• Ruby mongo driver

• github.com/mongodb/mongo-ruby-driver

• MongoMapper

• github.com/jnunemaker/mongomapper

• Mongoid

• github.com/durran/mongoid

Page 121: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MONGOMAPPER• DataMapper-like API

• good for moving from a sql schema to mongo

• easy to drop into rails

• works with rails 3

Page 122: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MONGOID• DataMapper-like API

• uses ActiveModel, so familiar validation syntax

• more opinionated towards embedded docs

• easy to drop into rails

• rails 2 - mongoid 1.x

• rails 3 - mongoid 2.x

Page 123: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

mongodb.org

Page 124: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

MOST IMPORTANTLY

最も重要な...

Page 125: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

WE MUST ARRIVE AT THE WHISKEY BAR EARLIER

TOMORROW我々はウイスキーバーは明日、以前に到着する必要があります

Page 126: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

BECAUSE IT CLOSES AT MIDNIGHT

;-)

それは真夜中に閉じるため

Page 127: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

QUESTIONS?

Page 128: Practical Ruby Projects with MongoDB - Ruby Kaigi 2010

THANKS!