MongoMapper
Mapping Ruby To and From Mongo




MongoSF San Francisco, CA   John Nunemaker
April 30, 2010                    Ordered List
Using
Extending
Prophesying
Using
Extending
Prophesying
...and many more.
class Item
end
class Item
  include MongoMapper::Document
end
class Datum
  include MongoMapper::EmbeddedDocument
end
Free Stu
Free Stu
 Persistence
Free Stu
 Persistence
 Validations   [presence, length, inclusion, ...]
Free Stu
 Persistence
 Validations   [presence, length, inclusion, ...]


 Callbacks     [before/after validate, create, save, ...]
Free Stu
 Persistence
 Validations    [presence, length, inclusion, ...]


 Callbacks      [before/after validate, create, save, ...]


 Associations   [many, belongs_to, one, ...]
Free Stu
 Persistence
 Validations     [presence, length, inclusion, ...]


 Callbacks       [before/after validate, create, save, ...]


 Associations    [many, belongs_to, one, ...]


 Serialization   [to_json]
Persistence
Never gonna give you up
item = Item.create({
   :title    => 'MongoSF',
   :location => 'San Fran',
   :when     => Time.now
})
puts item.to_mongo

{
    "_id"        =>   ObjectID('4bd8cc5cbcd1b313b3000001'),
    "title"      =>   "MongoSF",
    "location"   =>   "San Fran",
    "when"       =>   Wed Apr 28 17:01:32 -0700 2010
}
item              =   Item.new
item[:title]      =   'MongoSF'
item[:location]   =   'San Fran'
item[:when]       =   Time.now
item.save
puts item.to_mongo

{
    "_id"        =>   ObjectID('4bd8cc5cbcd1b313b3000001'),
    "title"      =>   "MongoSF",
    "location"   =>   "San Fran",
    "when"       =>   Wed Apr 28 17:01:32 -0700 2010
}
Types
What you be baby boo?
class Item
  include MongoMapper::Document

  key :title, String
  key :path, String
end
But Mongo is Schema-less?
Instead of database schema

Think App Schema
Built-in Types
Array, Binary, Boolean, Date, Float, Hash,
Integer, Nil, ObjectId, Set, String, Time
Custom Types
Its shake and bake and I helped!
class Set
  def self.to_mongo(value)
    value.to_a
  end

  def self.from_mongo(value)
    Set.new(value || [])
  end
end
class DowncasedString
  def self.to_mongo(value)
    value.nil? ? nil : value.to_s.downcase
  end

  def self.from_mongo(value)
    value.nil? ? nil : value.to_s.downcase
  end
end
class User
  include MongoMapper::Document

  key :email, DowncasedString
end
Typeless
I do not know who I am
class Foo
  include MongoMapper::Document
  key :bar
end

foo = Foo.new

foo.bar = 'Some text'
# foo.bar => "Some text"

foo.bar = 24
# foo.bar => 24
Validations
Currently using fork of validatable
class Item
  include MongoMapper::Document

  key :title, String
  validates_presence_of :title
end
class Item
  include MongoMapper::Document

  key :title, String, :required => true
end
validates_presence_of
validates_length_of
validates_format_of
validates_numericality_of
validates_acceptance_of
validates_confirmation_of
validates_inclusion_of
validates_exclusion_of
Callbacks
Ripped from AS2’s cold, dead fingers
class Item
  include MongoMapper::Document

  key :title, String
  key :path, String
  key :parent_id, ObjectId
  belongs_to :parent

  before_validation :set_path

  private
    def set_path
      self.path = parent.path + title.parameterize
    end
end
:before_save,                   :after_save,
:before_create,                 :after_create,
:before_update,                 :after_update,
:before_validation,             :after_validation,
:before_validation_on_create,   :after_validation_on_create,
:before_validation_on_update,   :after_validation_on_update,
:before_destroy,                :after_destroy,
:validate_on_create,            :validate_on_update,
:validate
Associations
I belong to you
to Docs
belongs_to, one, many, many :in
class Account
  include MongoMapper::Document

  many :sites
end

class Site
  include MongoMapper::Document

  key :account_id, ObjectId
  belongs_to :account
end
account = Account.create(:title => 'OL', :sites => [
   Site.new(:title => 'OL', :domain => 'orderedlist.com'),
   Site.new(:title => 'RT', :domain => 'railstips.org'),
])
[
    {
         '_id'          =>   ObjectID('...'),
         'title'        =>   'OL',
         'domain'       =>   'orderedlist.com'
         'account_id'   =>   ObjectID('...'),
    },
    {
         '_id'          =>   ObjectID('...'),
         'title'        =>   'RT',
         'domain'       =>   'railstips.org'
         'account_id'   =>   ObjectID('...'),
    }
]
to Embedded Docs
many, one
class Item
  include MongoMapper::Document

  many :data
end

class Datum
  include MongoMapper::EmbeddedDocument

  key :key, String
  key :value
end
Item.create(:title => 'MongoSF', :data => [
   Datum.new(:key => 'description', :value => 'Awesome.')
])
{
    '_id'   => ObjectID('...'),
    'title' => 'MongoSF',
    'data' => [
      {
        '_id'   => ObjectID('...'),
        'key'   => 'description'
        'value' => 'Awesome.',
      }
    ]
}
Using
Extending
Prophesying
Plugins
Conventional way to extend
MongoMapper is

Powered by Plugins
associations, callbacks, clone, descendants,
dirty, equality, identity_map, inspect, keys,
logger, modifiers, pagination, persistence,
protected, rails, serialization, timestamps,
userstamps, validations
module MongoMapper
  module Plugins
    def plugins
      @plugins ||= []
    end

    def plugin(mod)
      extend mod::ClassMethods     if mod.const_defined?(:ClassMethods)
      include mod::InstanceMethods if mod.const_defined?(:InstanceMethods)
      mod.configure(self)          if mod.respond_to?(:configure)
      plugins << mod
    end
  end
end
module ActsAsListFu
  module ClassMethods
    def reorder(ids)
      # reorder ids...
    end
  end

  module InstanceMethods
    def move_to_top
      # move to top
    end
  end

  def self.configure(model)
    model.key :position, Integer, :default => 1
  end
end
class Foo
  include MongoMapper::Document
  plugin ActsAsListFu
end

Foo.reorder(...)
Foo.new.move_to_top
Good Example
Joint: github.com/jnunemaker/joint
class Asset
  include MongoMapper::Document
  plugin Joint

  attachment :image
  attachment :file
end
asset = Asset.create({
   :image => File.open('john.jpg', 'r'),
   :file  => File.open('foo.txt', 'r'),
})

asset.image.id
asset.image.name
asset.image.type
asset.image.size
asset.image.read
Descendant Appends
Fancy Schmancy and Stolen
module FancySchmancy
  def some_method
    puts 'some method'
  end
end

MongoMapper::Document.append_extensions(FancySchmancy)

class Foo
  include MongoMapper::Document
end

Foo.some_method     # puts 'some method'
Foo.new.some_method # NoMethodError
module FancySchmancy
  def some_method
    puts 'some method'
  end
end

MongoMapper::Document.append_inclusions(FancySchmancy)

class Foo
  include MongoMapper::Document
end

Foo.new.some_method # puts 'some method'
Foo.some_method     # NoMethodError
module FancySchmancy
  def some_method
    puts 'some method'
  end
end

class Foo
  include MongoMapper::Document
end

MongoMapper::Document.append_extensions(FancySchmancy)

class Bar
  include MongoMapper::Document
end

Foo.some_method # puts 'some method'
Bar.some_method # puts 'some method'
module IdentityMapAddition
  def self.included(model)
    model.plugin MongoMapper::Plugins::IdentityMap
  end
end

MongoMapper::Document.append_inclusions(IdentityMapAddition)
Using
Extending
Prophesying
Active Model
Validations, callbacks, serialization, etc.
Blank Document
Mix and match whatever you want
Mongo::Query
Fancy query magic for the ruby driver
github.com/jnunemaker/mongo-query
ideafoundry.info/mongodb
mongotips.com
railstips.org
Thank you!
john@orderedlist.com
@jnunemaker



MongoSF San Francisco, CA   John Nunemaker
April 30, 2010                    Ordered List

Ruby Development and MongoMapper (John Nunemaker)