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
== What's different about Mongomatic?
== 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
Mongomatic.db = Mongo::Connection.new.db("mongomatic_test")
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
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
Additional contributors can be viewed at: https://github.com/mongomachine/mongomatic/contributors
== Copyright
Copyright (c) 2010-2011 Ben Myles. See LICENSE for details.