module Mongoo class AlreadyInsertedError < Exception; end class NotInsertedError < Exception; end class InsertError < Exception; end class StaleUpdateError < Exception; end class UpdateError < Exception; end class RemoveError < Exception; end class NotValidError < Exception; end class DbNameNotSet < Exception; end class ConnNotSet < Exception; end module Persistence def self.included(base) base.extend(ClassMethods) end module ClassMethods def collection_name(val=nil) if val @collection_name = val @collection = nil @collection_name else @collection_name ||= self.model_name.tableize end end def collection @collection ||= db.collection(collection_name) end def conn @conn ||= begin Mongoo.conn || raise(Mongoo::ConnNotSet) end end def conn=(conn) @conn = conn @db = nil @collection = nil @conn end def db @db ||= conn.db(db_name) end def db_name @db_name ||= begin Mongoo.db_name || raise(Mongoo::DbNameNotSet) end end def db_name=(db_name) @db_name = db_name @db = nil @collection = nil @db_name end def find(query={}, opts={}) Mongoo::Cursor.new(self, collection.find(query, opts)) end def find_one(query={}, opts={}) id_map_on = Mongoo::IdentityMap.on? is_simple_query = Mongoo::IdentityMap.simple_query?(query, opts) if id_map_on && is_simple_query if doc = Mongoo::IdentityMap.read(query) return doc end end if doc = collection.find_one(query, opts) doc = new(doc, true) Mongoo::IdentityMap.write(doc) if id_map_on && is_simple_query doc else nil end end def all find end def each find.each { |found| yield(found) } end def first find.limit(1).next_document end def empty? count == 0 end def count collection.count end def drop collection.drop end def index_meta Mongoo::INDEX_META[self.collection_name] ||= {} end def index(spec, opts={}) self.index_meta[spec] = opts end def create_indexes self.index_meta.each do |spec, opts| opts[:background] = true if !opts.has_key?(:background) collection.create_index(spec, opts) end; true end end # ClassMethods def to_param persisted? ? get("_id").to_s : nil end def to_key get("_id") end def to_model self end def persisted? @persisted == true #!get("_id").nil? end def collection self.class.collection end def insert(opts={}) ret = _run_insert_callbacks do if persisted? raise AlreadyInsertedError, "document has already been inserted" end unless valid? if opts[:safe] == true raise Mongoo::NotValidError, "document contains errors" else return false end end ret = self.collection.insert(mongohash.deep_clone, opts) unless ret.is_a?(BSON::ObjectId) raise InsertError, "not an object: #{ret.inspect}" end set("_id", ret) @persisted = true set_persisted_mongohash(mongohash.deep_clone) ret end Mongoo::IdentityMap.write(self) if Mongoo::IdentityMap.on? ret end def insert!(opts={}) insert(opts.merge(:safe => true)) end def update(opts={}) _run_update_callbacks do unless persisted? raise NotInsertedError, "document must be inserted before being updated" end unless valid? if opts[:safe] == true raise Mongoo::NotValidError, "document contains errors" else return false end end opts[:only_if_current] = true unless opts.has_key?(:only_if_current) opts[:safe] = true if !opts.has_key?(:safe) && opts[:only_if_current] == true update_hash = build_update_hash(self.changelog) return true if update_hash.empty? update_query_hash = build_update_query_hash(persisted_mongohash.to_key_value, self.changelog) if Mongoo.verbose_debug puts "\n* update_query_hash: #{update_query_hash.merge({"_id" => get("_id")}).inspect}\n update_hash: #{update_hash.inspect}\n opts: #{opts.inspect}\n" end ret = self.collection.update(update_query_hash.merge({"_id" => get("_id")}), update_hash, opts) if !ret.is_a?(Hash) || (ret["updatedExisting"] && ret["n"] == 1) set_persisted_mongohash(mongohash.deep_clone) @persisted = true true else if opts[:only_if_current] raise StaleUpdateError, ret.inspect else raise UpdateError, ret.inspect end end end end def update!(opts={}) update(opts.merge(:safe => true)) end def destroyed? @destroyed != nil end def new_record? !persisted? end def remove(opts={}) _run_remove_callbacks do unless persisted? raise NotInsertedError, "document must be inserted before it can be removed" end ret = self.collection.remove({"_id" => get("_id")}, opts) if !ret.is_a?(Hash) || (ret["err"] == nil && ret["n"] == 1) @destroyed = true @persisted = false true else raise RemoveError, ret.inspect end end end def remove!(opts={}) remove(opts.merge(:safe => true)) end def reload init_from_hash(collection.find_one(get("_id"))) @persisted = true set_persisted_mongohash(mongohash.deep_clone) true end def build_update_hash(changelog) update_hash = {} changelog.each do |op, k, v| update_hash["$#{op}"] ||= {} update_hash["$#{op}"][k] = v end update_hash end protected :build_update_hash def build_update_query_hash(persisted_mongohash_kv, changelog) update_query_hash = {} changelog.each do |op, k, v| if persisted_val = persisted_mongohash_kv[k] if persisted_val == [] # work around a bug where mongo won't find a doc # using an empty array [] if an index is defined # on that field. persisted_val = { "$size" => 0 } end update_query_hash[k] = persisted_val end end update_query_hash end protected :build_update_query_hash end end