Home > mongomatic

mongomatic

Mongomatic is a project mainly written in ..., based on the Apache-2.0 license.

= Mongomatic

Mongomatic allows you to map your Ruby objects to Mongo documents. It is designed to be fast and simple.

=== Release Notes

This project follows semantic versioning as detailed here (http://semver.org). This means that minor version bumps (0.x => 0.y) can break compatibility. Please see the CHANGELOG for upgrade notes.

=== Note on Patches/Pull Requests

  • Fork the project.
  • Make your feature addition or bug fix.
  • Add tests for it. This is important so I don't break it in a future version unintentionally.
  • Commit just the modifications, do not mess with Rakefile, VERSION, or CHANGELOG.
  • Add a separate commit with modifications to the CHANGELOG file
  • Send me a pull request. Bonus points for branches.

== What's different about Mongomatic?

  • Follows MongoDB idioms wherever possible.
  • Only strives to do "just enough" and never too much.
  • When possible, we defer to MongoDB. For example, there's no Mongomatic query API, instead you use the same query hash syntax you would with the Ruby MongoDB driver.
  • No complex relationship management: if you want to model relationships then add your own finder methods.
  • Minimal dependencies.

== Basic Usage

require 'mongomatic'

class User < Mongomatic::Base def validate self.errors.add "name", "can't be empty" if self["name"].blank? self.errors.add "email", "can't be empty" if self["email"].blank? end end

set the db for all models:

Mongomatic.db = Mongo::Connection.new.db("mongomatic_test")

or you can set it for a specific model:

User.db = Mongo::Connection.new.db("mongomatic_test_user")

User.empty? => true

u = User.new(:name => "Ben") => #<User:0x00000100d0cbf8 @doc={"name"=>"Ben"}, @removed=false> u.valid? => false u["email"] = "[email protected]" u.valid? => true u.insert => BSON::ObjectId('4c32834f0218236321000001')

User.empty? => false

u["name"] = "Ben Myles" => "Ben Myles" u.update => 137

found = User.find_one({"name" => "Ben Myles"}) => #<User:0x00000101939a48 @doc={"_id"=>BSON::ObjectId('4c32834f0218236321000001'), "name"=>"Ben Myles", "email"=>"[email protected]"}, @removed=false, @is_new=false, @errors=[]> User.find_one(BSON::ObjectId('4c32834f0218236321000001')) == found => true

cursor = User.find({"name" => "Ben Myles"}) => #<Mongomatic::Cursor:0x0000010195b4e0 @obj_class=User, @mongo_cursor=<Mongo::Cursor:0x80cadac0 namespace='mongomatic_test.User' @selector={"name"=>"Ben Myles"}>> found = cursor.next => #<User:0x00000101939a48 @doc={"_id"=>BSON::ObjectId('4c32834f0218236321000001'), "name"=>"Ben Myles", "email"=>"[email protected]"}, @removed=false, @is_new=false, @errors=[]> found.remove => 67 User.count => 0 User.find({"name" => "Ben Myles"}).next => nil

== Indexes

Mongomatic doesn't do anything special to support indexes, but here's the suggested convention:

class Person < Mongomatic::Base
class << self def create_indexes collection.create_index("email", :unique => true) end end end

You can run Person.create_indexes whenever you add new indexes, it won't throw an error if they already exist.

If you have defined a unique index and want Mongomatic to raise an exception on a duplicate insert you need to use insert! or update!. The error thrown will be Mongo::OperationFailure. See the test suite for examples.

== Validations

You can add validations to your model by creating a validate method. If your validate method pushes anything into the self.errors object your model will fail to validate, otherwise if self.errors remains empty the validations will be taken to have passed.

class Person < Mongomatic::Base
def validate self.errors.add "name", "blank" if self["name"].blank? self.errors.add "email", "blank" if self["email"].blank? self.errors.add "address.zip", "blank" if (self["address"] || {})["zip"].blank? end end

p = Person.new => #<Person:0x000001018c2d58 @doc={}, @removed=false, @is_new=true, @errors=[]> p.valid? => false p.errors => [["name", "blank"], ["email", "blank"], ["address.zip", "blank"]] p.errors.full_messages => ["name blank", "email blank", "address.zip blank"] p["name"] = "Ben" p["email"] = "Myles" p["address"] = { "zip" => 94107 } p.valid? => true

=== The Validation Helper (Expectations)

To make writing your validate method a little simpler you can use Mongomatic::Expectations. You must include Mongomatic::Expectations::Helper on any class you wish to use them. You can use expectations like this:

class Person < Mongomatic::Base include Mongomatic::Expectations::Helper

 def validate
   expectations do
     be_present   self['name'], ["name", "cannot be blank"]
     not_be_match self['nickname'], ["nickname", "cannot contain an uppercase letter"], :with => /[A-Z]/
   end
 end

end

p = Person.new p.valid? => false p.errors.full_messages => ["Name cannot be blank"] p['name'] = 'Jordan' p.valid? => true p['name'] = nil p['nickname'] = 'Jordan' p.valid? p.errors.full_messages => ["Name cannot be blank", "Nickname cannot contain an uppercase letter"]

You can use any of these expectations inside of the expectations block:

  • be_expected value, error_message - validates that value is true

  • not_be_expected value, error_message - validates that value is false

  • [not]_be_present value, error_message - validates presence of value

  • [not]_be_a_number value, error_message, options - validates that value is/is not a number Can be a string containing only a number). Only takes :allow_nil option

  • [not]_be_match value, error_message, options - validates that value matches/does not match regular expression specified by option :with. Also, takes option :allow_nil

  • be_of_length value, error_message, options - validates that value is any of: less than the number specified in the :minimum option, greater than the number specified in option :maximum, or in range specified by option :range

== Relationships

Mongomatic doesn't have any kind of special support for relationship management but it's easy to add this to your models where you need it. Here's an example that models Twitter-like followers on a User model:

class User < Mongomatic::Base
class << self def create_indexes self.collection.create_index("following_ids") end end

def follow(user)
  self.push("following_ids", user["_id"])
end

def unfollow(user)
  self.pull("following_ids", user["_id"])
end

def following
  return nil if new?
  self.class.find( { "_id" => { "$in" => (self["following_ids"] || []) } } )
end

def followers
  return nil if new?
  self.class.find( { "following_ids" => self["_id"] } )
end

def friends
  return nil if new?
  self.class.find( { "following_ids" => self["_id"], 
                     "_id" => { "$in" => (self["following_ids"] || []) } } )
end

end

== Observers

Mongomatic does have one thing in common with ActiveRecord and that is observers. You can add observers to your class to decouple distinct functionality in callbacks. To add observation to your model you must include the Mongomatic::Observable module. All observers inherit from Mongomatic::Observer.

Unlike ActiveRecord the existence of a CollectionNameObserver will automatically add the observer the CollectionName class. Instead you must use the observer macro or CollectionName.add_observer

You can add your own observers to CollectionName using CollectionName.add_observer(klass).

class MyCustomCallbacks < Mongomatic::Observer def after_insert_or_update puts "after insert or update" end end

class Person < Mongomatic::Base include Mongomatic::Observable observer :PersonObserver observer MyCustomCallbacks end

this observer is automatically added to the Person class

class PersonObserver < Mongomatic::Observer def after_insert(instance) puts "new person inserted" end end

It is worth noting that you should be careful the operations you perform on the instance of your class passed to the observer callbacks. Calling operations that invoke callbacks can result in an infinite loop if improperly structured.

== Contributors

  • Ben Myles -- http://github.com/benmyles
  • Justin Smestad -- http://github.com/jsmestad
  • Jordan West -- http://github.com/jrwest

Additional contributors can be viewed at: https://github.com/mongomachine/mongomatic/contributors

== Copyright

Copyright (c) 2010-2011 Ben Myles. See LICENSE for details.