Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Practical Ruby Projects With Mongo Db

9,992 views

Published on

Given at LaRuby meetup on 4/8/2010

Published in: Technology

Practical Ruby Projects With Mongo Db

  1. 1. Practical Ruby Projects with
  2. 2. Who am I? Alex Sharp Amphibious Code Creature at OptimisDev @ajsharp alexjsharp.tumblr.com
  3. 3. We’re going to talk about two things.
  4. 4. 1. Why/how MongoDB is practical
  5. 5. 2. A few examples of how to use MongoDB with Ruby
  6. 6. So what’s all this “practical” talk?
  7. 7. Imagine if databases were cars...
  8. 8. Any takers for MySQL?
  9. 9. RDBMS (i.e. mysql) =~
  10. 10. In other words...
  11. 11. MySQL is very reliable.
  12. 12. But it may not be a speed demon.
  13. 13. Guesses for MongoDB?
  14. 14. MongoDB =~
  15. 15. Mongo is reliable too, but they’re also go pretty damn fast.
  16. 16. The Practicality of MongoDB
  17. 17. Simplifying schema design Objects weren’t made for SQL schemas
  18. 18. Simplifying schema design Objects weren’t meant to be “mapped” to a SQL schema
  19. 19. Simplifying schema design We’re forced to create “relationships” when what we really want is properties for our objects.
  20. 20. Simplifying schema design Non-relational databases are better suited for objects
  21. 21. Simplifying schema design Maps/hashes/associative arrays are arguably among the best object serialization formats
  22. 22. @alex = Person.new( :name => "alex", :friends => [Friend.new("Jim"), Friend.new("Bob")] )
  23. 23. Native ruby object <Person:0x10017d030 @name="alex", @friends= [#<Friend:0x10017d0a8 @name="Jim">, #<Friend:0x10017d058 @name="Bob"> ]>
  24. 24. JSON Representation @alex.to_json { name: "alex", friends: [{ name: "Jim" }, { name: "Bob" }] }
  25. 25. SQL Schema Representation # in a SQL schema people: - name friends: - name - friend_id
  26. 26. Ruby -> JSON -> SQL <Person:0x10017d030 @name="alex", @friends= Ruby [#<Friend:0x10017d0a8 @name="Jim">, #<Friend:0x10017d058 @name="Bob"> ]> @alex.to_json JSON { name: "alex", friends: [{ name: "Jim" }, { name: "Bob" }] } people: - name SQL friends: - name - friend_id
  27. 27. Ruby -> JSON -> SQL <Person:0x10017d030 @name="alex", @friends= Ruby [#<Friend:0x10017d0a8 @name="Jim">, #<Friend:0x10017d058 @name="Bob"> ]> @alex.to_json JSON { name: "alex", friends: [{ name: "Jim" }, { name: "Bob" }] } people: - name Feels like we’re having SQL to work too hard here friends: - name - friend_id
  28. 28. Mongo Representation @alex.to_json { name: "alex", friends: [{ name: "Jim" }, { name: "Bob" }] }
  29. 29. Mongo Representation @alex.to_json { name: "alex", friends: [{ name: "Jim" }, { name: "Bob" }] } In Mongo, friends is an “embedded document”
  30. 30. Mongo Representation @alex.to_json { name: "alex", friends: [{ name: "Jim" }, { name: "Bob" }] } Great for one-to-many associations
  31. 31. Mongo Representation @alex.to_json { name: "alex", friends: [{ name: "Jim" }, { name: "Bob" }] } No JOINS required.
  32. 32. Mongo Representation @alex.to_json { name: "alex", friends: [{ name: "Jim" }, { name: "Bob" }] } This is a good thing for scalability, because JOINS limit our ability to horizontally scale.
  33. 33. Mongo Representation @alex.to_json { name: "alex", friends: [{ name: "Jim" }, { name: "Bob" }] } But more importantly, this seems more intuitive than messing with foreign keys
  34. 34. Ok, so what? You’re probably thinking... “Listen GUY, SQL isn’t that bad”
  35. 35. Ok, so what? True.
  36. 36. Ok, so what? SQL is actually really, really good at what it was designed to do.
  37. 37. Ok, so what? But SQL schemas are designed for storing and querying data, not necessarily modeling objects.
  38. 38. Ok, so what? It is called Structured Query Language.
  39. 39. Ok, so what? And to talk to these schemas in software, we use ORMs.
  40. 40. Ok, so what? ORM stands for Object Relational Mapper
  41. 41. Ok, so what? We need ORMs to bridge the gap between SQL and native objects (in our case, Ruby objects)
  42. 42. Ok, so what? I don’t want to map my objects to a schema designed for querying data.
  43. 43. Ok, so what? I want to store my objects in a datastore that was designed for storing objects
  44. 44. Ok, so what? Mongo is great for this b/c it stores objects (documents) as binary JSON
  45. 45. Ok, so what? And as we’ve seen, JSON is great for representing native objects
  46. 46. Ok, so what? This is important because when I’m writing an application, I don’t want to accomodate my objects to my datastore.
  47. 47. Ok, so what? I want something flexible that stays out of my way, but doesn’t sacrifice performance.
  48. 48. In a Nutshell Schema design for humans, not machines i.e. document-oriented is awesome schema-less is for adults Pragmatic balance of performance and functionality Speed/performance of mongo is super awesome Scalability features are really powerful too
  49. 49. Practical Projects
  50. 50. Three Examples 1.Blogging Application 2.Accounting Application 3.Logging
  51. 51. Blogging Application
  52. 52. Blogging Application Much easier to model with Mongo than a relational database
  53. 53. Blogging Application A post has an author
  54. 54. Blogging Application A post has an author A post has many tags
  55. 55. Blogging Application A post has an author A post has many tags A post has many comments
  56. 56. Blogging Application 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.
  57. 57. require 'mongo' conn = Mongo::Connection.new.db('bloggery') posts = conn.collection('posts') authors = conn.collection('authors')
  58. 58. # returns a Mongo::ObjectID object alex = authors.save :name => "Alex" post = posts.save( :title => 'Post title', :body => 'Massive potification...', :tags => ['laruby', 'omg', 'lolcats'], :comments => [ { :name => "Loudmouth McGee", :email => 'loud@mouth.edu', :body => "Something really ranty..." } ], :author_id => alex )
  59. 59. # returns a Mongo::ObjectID object alex = authors.save :name => "Alex" post = posts.save( :title => 'Post title', :body => 'Massive potification...', :tags => ['laruby', 'omg', 'lolcats'], :comments => [ { :name => "Loudmouth McGee", :email => 'loud@mouth.edu', :body => "Something really ranty..." } ], Joins not necessary. Sweet. :author_id => alex )
  60. 60. Logging with Capped Collections
  61. 61. Capped collections Fixed-sized, limited operation, auto age-out collections (kinda like memcached) Fixed insertion order Super fast (faster than normal writes) Ideal for logging and caching
  62. 62. This is awesome.
  63. 63. Now we have logs we can query (and analyze)
  64. 64. Can also be used for troubleshooting when things go wrong
  65. 65. Bunyan Thin ruby layer around a Mongo capped collection
  66. 66. require 'bunyan' Bunyan::Logger.configure do |c| c.database 'my_bunyan_db' c.collection 'development_log' # == 100.megabytes if using rails c.size 104857600 end Bunyan::Logger.save( :request_method => 'get', :status_code => 200 )
  67. 67. A simple accounting application (maybe)
  68. 68. The object model ledger * transactions * entries
  69. 69. The object model # Credits Debits Transaction { 1 { :account => “Cash”, :amount => 100.00 } { :account => “Notes Pay.”, :amount => 100.00 } Ledger Entries Transaction { 2 { :account => “A/R”, :amount => 25.00 } { :account => “Gross Revenue”, :amount => 25.00 }
  70. 70. Object model summary Each ledger transaction belongs to a ledger. Each ledger transaction has two ledger entries which must balance.
  71. 71. Object Model with ActiveRecord @credit_entry = LedgerEntry.new :account => "Cash", :amount => 100.00, :type => "credit" @debit_entry = LedgerEntry.new :account => "Notes Pay.", :amount => 100.00, :type => "debit" @ledger_transaction = LedgerTransaction.new :ledger_id => 1, :ledger_entries => [@credit_entry, @debit_entry]
  72. 72. Object Model with Mongo @ledger_transaction = LedgerTransaction.new :ledger_id => 1, :ledger_entries => [ { :account => 'Cash', :type => "credit", :amount => 100.00 }, { :account => 'Notes Pay.', :type => "debit", :amount => 100.00 } ]
  73. 73. Object Model with Mongo @ledger_transaction = LedgerTransaction.new :ledger_id => 1, :ledger_entries => [ { :account => 'Cash', :type => "credit", :amount => 100.00 }, { :account => 'Notes Pay.', :type => "debit", :amount => 100.00 } ] This is the perfect case for embedded documents.
  74. 74. Object Model with Mongo @ledger_transaction = LedgerTransaction.new :ledger_id => 1, :ledger_entries => [ { :account => 'Cash', :type => "credit", :amount => 100.00 }, { :account => 'Notes Pay.', :type => "debit", :amount => 100.00 } ] We would never have a ledger entry w/o a transaction.
  75. 75. Using mongo w/ Ruby Ruby mongo driver MongoMapper MongoID Many other ORM/ODM’s under active development
  76. 76. MongoMapper
  77. 77. MongoMapper • MongoDB “ORM” developed by John Nunemaker
  78. 78. MongoMapper • MongoDB “ORM” developed by John Nunemaker • author of HttpParty
  79. 79. MongoMapper • MongoDB “ORM” developed by John Nunemaker • author of HttpParty • Very similar syntax to DataMapper
  80. 80. MongoMapper • MongoDB “ORM” developed by John Nunemaker • author of HttpParty • Very similar syntax to DataMapper • Declarative rather than inheritance-based
  81. 81. MongoMapper • MongoDB “ORM” developed by John Nunemaker • author of HttpParty • Very similar syntax to DataMapper • Declarative rather than inheritance-based • Very easy to drop into rails
  82. 82. MongoMapper class Post include MongoMapper::Document belongs_to :author, :class_name => "User" key :title, String, :required => true key :body, String key :author_id, Integer, :required => true key :published_at, Time key :published, Boolean, :default => false timestamps! many :tags end class Tag include MongoMapper::EmbeddedDocument key :name, String, :required => true end
  83. 83. Questions?
  84. 84. Thanks!

×