New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

mongo_odm

Package Overview
Dependencies
Maintainers
1
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

mongo_odm

  • 0.2.20
  • Rubygems
  • Socket score

Version published
Maintainers
1
Created
Source

= mongo_odm

Flexible persistence module for any Ruby class to MongoDB.

= Why another ODM for MongoDB?

  • Fully compatible with Rails 3
  • Use the Mongo ruby driver when possible (query syntax, cursors, indexes management...)
  • Allow lazy loading of collections and queries nesting (concatenation of 'find' calls) to emulate ActiveRecord 3
  • No association methods (for now): Just declare your own methods on models to fetch the related items
  • Give support for dirty objects, validations, etc. through ActiveModel 3
  • Automanage type conversions and default values
  • Keep it as simple as possible

= Basics

Other Mongo ODMs don't require to explicitly define the possible schema of a model. I think this is necessary to help with type conversions (instanciate the right class for each attribute, and convert them to a Mongo compatible type when persisted). But it's also possible to fill attributes with valid Mongo values without defining them as fields, and only the attributes whose values are different than the default values are stored as part of the document when saved.

A piece of code is better than a hundred of words:

Establish connection; it uses localhost:27017 and database 'test' if not specified

MongoODM.config = {:host => 'localhost', :port => 27017, :database => "my_tests"}

class Shape include MongoODM::Document field :name field :x, Float, :default => 0.0 field :y, Float, :default => 0.0 end

shape = Shape.new(:name => "Point", :x => 0, :y => 5) shape.save

Saves:

{ "_id" : ObjectId("4be97178715dd2c4be000006"),

"_class" : "Shape",

"x" : 0,

"y" : 5,

"color" : null,

"name" : "Point"

}

class Circle < Shape # This items are stored on the 'shapes' collection field :radius, Float, :default => 1.0 end

circle = Circle.new.save

Saves:

{ "_id" : ObjectId("4be97203715dd2c4be000007"),

"_class" : "Circle",

"x" : 1,

"y" : 1,

"color" : null,

"radius" : 1 }

all_shapes = Shape.find # Returns a criteria object. It will execute the query and instance the objects once you iterate over it

all_shapes.to_a

Returns all the shapes; notice they are of different classes:

[ #<Shape x: 0.0, y: 5.0, color: nil, name: "Point", _id: {"$oid"=>"4be97178715dd2c4be000006"}>,

#<Circle x: 1.0, y: 1.0, color: nil, radius: 1.0, _id: {"$oid"=>"4be97293715dd2c4be000008"}> ]

In fact, you can instanciate any document stored as a hash to the appropiate class. The document just need to have the attribute "_class" set to the name of the class you want to use as the object type. Example:

MongoODM.instanciate({ :x => 12, :y => 5, '_class' => 'Circle' })

Returns:

#<Circle x: 12.0, y: 5.0, color: nil, radius: 1.0>

And because any query method returns a MongoODM::Criteria object, you can concatenate them to nest several conditions (like if they were ActiveRecord scopes):

Shape.find(:radius => 1).find({}, {:sort => [:color, :asc]}) # Returns a criteria object. Once you iterate over it, it will run a query with both the :radius selector and :sort order.

You can also define your own class methods that returns criteria objects, and concatenate them to obtain a single criteria with all the conditions merged in the calls order:

class Shape include MongoODM::Document

def self.with_radius(n)
  find(:radius => n)
end

def self.ordered_by_color
  find({}, {:sort => [:color, :asc]})
end

end

Shape.with_radius(1).ordered_by_color # Returns the same criteria than the previous example

Default values for fields can be either a fixed value, or a block, in which case the block will be called each time an object is instantiated. Example:

class Timestamp include MongoODM::Document

field :value, Time, :default => lambda { Time.now }
field :set, Set, :default => lambda { Set.new }

end

Take a look at the Mongo Ruby driver documentation for the 'find' method to see the available options:

http://api.mongodb.org/ruby/1.2.4/Mongo/Collection.html#find-instance_method

= Collections

By default, mongo_odm stores data on a collection with the same name than the class, pluralized. In case of class inheritance, it uses the name of the parent class. You can override this behavior by setting a different collection for a class:

class Shape set_collection 'my_shapes' end

Alternatively, you can pass a MongoODM::Collection instance to set_collection, to indicate not only a collection name, but also a different connection and/or database:

class Shape set_collection MongoODM::Collection.new(MongoODM.connection.db('another_database'), 'my_shapes') end

= References

You can use BSON::DBRef as the type of a field. This acts as a pointer to any other document in your database, at any collection. If you assign a MongoODM::Document instance to a BSON::DBRef field, it will be converted to a reference automatically. To instantiate any reference object, just call "dereference" on it. To convert any MongoODM::Document object to a reference, just call "to_dbref" on it.

You can even dereference a full array or hash that contains BSON::DBRef instances! It will dereference them at any level.

class Node include MongoODM::Document field :name field :parent, BSON::DBRef field :children, Array end

root_node = Node.new(:name => 'root') root_node.save children1 = Node.new(:name => 'children1', :parent => root_node) children1.save root_node.children = [children1.to_dbref] root_node.save

children1.parent # Returns BSON::DBRef(namespace:"nodes", id: "4d60e8c83f5f19cf08000001") root_node.children # Returns [BSON::DBRef(namespace:"nodes", id: "4d60e8c83f5f19cf08000002")]

children1.parent.dereference # Returns #<Node _id: BSON::ObjectId('4d60e8c83f5f19cf08000001'), children: [BSON::DBRef(namespace:"nodes", id: "4d60e8c83f5f19cf08000002")], name: "root", parent: nil>

root_node.children.dereference # Returns [#<Node _id: BSON::ObjectId('4d60e8c83f5f19cf08000002'), children: nil, name: "children1", parent: BSON::DBRef(namespace:"nodes", id: "4d60e8c83f5f19cf08000001")>]

= Associations

To embed just one copy of another class, just define the field type of that class. The class just need to respond to the "type_cast" class method and the "to_mongo" instance method. Example:

class RGB def initialize(r, g, b) @r, @g, @b = r, g, b end

def inspect
  "RGB(#{@r},#{@g},#{@b})"
end

def to_mongo
  [@r, @g, @b]
end

def self.type_cast(value)
  return nil if value.nil?
  return value if value.is_a?(RGB)
  return new(value[0], value[1], value[2]) if value.is_a?(Array)
end

end

class Color include MongoODM::Document field :name field :rgb, RGB

index :name, :unique => true

end

Color.create_indexes # You can also use MongoODM.create_indexes to create all the indexes at all classes at the same time

color = Color.new(:name => "red", :rgb => RGB.new(255,0,0)) color.save

Saves:

{"_class":"Color","name":"red","rgb":[255,0,0],"_id":{"$oid": "4bf070fb715dd271c2000001"}}

red = Color.find({:name => "red"}).first

Returns:

#<Color name: "red", rgb: RGB(255,0,0), _id: {"$oid"=>"4bf070fb715dd271c2000001"}>

Of course, if the embedded object's class includes the MongoODM::Document module, you don't need to define those methods. Just define the field as that class:

class RGB include MongoODM::Document field :r, Fixnum field :g, Fixnum field :b, Fixnum end

class Color include MongoODM::Document field :name field :rgb, RGB end

color = Color.new(:name => "red", :rgb => RGB.new(:r => 255, :g => 0, :b => 0)) color.save

Saves:

{"_class":"Color","name":"red","rgb":{"_class":"RGB","r":255,"g":0,"b":0},"_id":{"$oid": "4bf073e3715dd27212000001"}}

red = Color.find({:name => "red"}).first

Returns:

#<Color name: "red", rgb: #<RGB r: 255, g: 0, b: 0>, _id: {"$oid"=>"4bf073e3715dd27212000001"}>

If you want to save a collection of objects, just define the field as an Array. You can even store objects of different types!

class Shape include MongoODM::Document field :x, Float field :y, Float end

class Circle < Shape include MongoODM::Document field :radius, Float end

class Line < Shape include MongoODM::Document field :dx, Float field :dy, Float end

class Draw include MongoODM::Document field :objects, Array end

circle1 = Circle.new(:x => 1, :y => 1, :radius => 10) circle2 = Circle.new(:x => 2, :y => 2, :radius => 20) line = Line.new(:x => 0, :y => 0, :dx => 10, :dy => 5)

draw = Draw.new(:objects => [circle1, line, circle2]) draw.save

Saves:

{ "_class" : "Draw",

"objects" : [ { "_class" : "Circle",

"x" : 1.0,

"y" : 1.0,

"color" : null,

"radius" : 10.0 },

{ "_class" : "Line",

"x" : 0.0,

"y" : 0.0,

"color" : null,

"dx" : 10.0,

"dy" : 5.0},

{ "_class" : "Circle",

"x" : 2.0,

"y" : 2.0,

"color" : null,

"radius" : 20.0 } ],

"_id":{"$oid": "4bf0775d715dd2725a000001"}}

Draw.find_one

Returns

#<Draw objects: [#<Circle x: 1.0, y: 1.0, color: nil, radius: 10.0>, #<Line x: 0.0, y: 0.0, color: nil, dx: 10.0, dy: 5.0>, #<Circle x: 2.0, y: 2.0, color: nil, radius: 20.0>], _id: {"$oid"=>"4bf0775d715dd2725a000001"}>

To reference the associated objects instead of embed them, you can use BSON::DBRef (to reference one), Array (to reference several), and others:

class Flag include MongoODM::Document field :colors_refs, Array, :default => []

def add_color(color)
  colors_refs << color.to_dbref
end

def colors
  colors_refs.dereference
end

end

class Color include MongoODM::Document field :name end

color_red = Color.new(:name => "red") color_red.save color_green = Color.new(:name => "green") color_green.save

flag = Flag.new flag.add_color(color_red) flag.add_color(color_green) flag.save

Saves:

{ "_id" : ObjectId("4be96c15715dd2c4be000003"),

"_class" : "Flag",

"colors_refs" : [

{ "$ns" : "colors",

"$id" : {

"$oid" : "4d60ea4e3f5f19cf10000001"

}

},

{ "$ns" : "colors",

"$id" : {

"$oid" : "4d60ea4e3f5f19cf10000002"

}

}

]

}

flag.colors # Returns [#<Color _id: BSON::ObjectId('4d60ea4e3f5f19cf10000001'), name: "red">, #<Color _id: BSON::ObjectId('4d60ea4e3f5f19cf10000002'), name: "green">]

flag.colors

Returns a criteria object that wraps a cursor

flag.colors.to_a

Returns:

[#<Color name: "red", _id: {"$oid"=>"4be96bfe715dd2c4be000001"}>, #<Color name: "green", _id: {"$oid"=>"4be96c08715dd2c4be000002"}>]

Or you can build your custon methods. Example:

class Flag include MongoODM::Document field :colors_ids, Array

def colors
  Color.find(:_id => {'$in' => colors_ids})
end

end

class Color include MongoODM::Document field :name end

Color.new(:name => "red").save Color.new(:name => "green").save

flag = Flag.new(:colors_ids => [ Color.find_one(:name => "red").id, Color.find_one(:name => "green").id ]) flag.save

Saves:

{ "_id" : ObjectId("4be96c15715dd2c4be000003"),

"_class" : "Flag",

"colors_ids" : [ ObjectId("4be96bfe715dd2c4be000001"), ObjectId("4be96c08715dd2c4be000002") ]

}

flag.colors

Returns a criteria object that wraps a cursor

flag.colors.to_a

Returns:

[#<Color name: "red", _id: {"$oid"=>"4be96bfe715dd2c4be000001"}>, #<Color name: "green", _id: {"$oid"=>"4be96c08715dd2c4be000002"}>]

= Callbacks

For now, the available callbacks are: after_initialize, before_save, after_save

Example:

class User include MongoODM::Document

field :encrypted_password
attr_accessor :password

before_save :encrypt_password

def encrypt_password
  return if self.password.blank?
  self.encrypted_password = encrypt(password)
end
protected :encrypt_password

end

= Validations

All the validation methods defined in ActiveModel::Validations are included

Example:

class User include MongoODM::Document field :email

validates_presence_of :email
validates_uniqueness_of :email, :case_sensitive => false
validates_format_of :email, :with => /^([a-zA-Z0-9_\.\-\+])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/

end

= Dirty

All the dirty object methods defined in ActiveModel::Dirty are included

Example:

class User include MongoODM::Document field :email end

user = User.new user.email = "hello@h1labs.com" user.email_changed? # Returns true user.email_change # Returns [nil, "hello@h1labs.com"] user.changes # Returns {"email" => [nil, "hello@h1labs.com"]}

= Others

Access to a cursor to the whole collection:

User.cursor

Use cursor methods directly on the class:

User.has_next? User.each{...} User.next_document User.rewind! ...

= TODO

  • Allow to specify different database connections with each document definition
  • Increase rspec coverage
  • Document, document, document!
  • Create useful modules to make common operations easier (versioning, trees, etc)

= More

For now, take a look at the Mongo Ruby driver syntax:

http://api.mongodb.org/ruby/1.2.4/index.html

= Credits

Carlos Paramio, http://h1labs.com.

See CONTRIBUTORS file for a list of contributions.

= License

See LICENSE file for details.

FAQs

Package last updated on 01 May 2011

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc