Skip to content

Commit

Permalink
[FEATURE] Mongomatic::Base#find_or_initialize method added for 0.9.pre
Browse files Browse the repository at this point in the history
  • Loading branch information
jsmestad committed May 24, 2011
1 parent 9dca4b2 commit 04056a4
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 42 deletions.
1 change: 1 addition & 0 deletions CHANGELOG
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
= 0.9.pre
* Add Mongomatic::Base#find_or_initialize method (jsmestad)

Internal:
* Updated development dependencies and Jeweler definitions (jsmestad)
Expand Down
4 changes: 2 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ source "http://rubygems.org"
# Add dependencies required to use your gem here.
# Example:
gem "activesupport", ">= 3.0.0"
gem "mongo", ">= 1.2.4"
gem "bson", ">= 1.2.4"
gem "mongo", ">= 1.3.1"
gem "bson", ">= 1.3.1"
gem "i18n", ">= 0.5.0"

# Add dependencies to develop your gem here.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.8.2
0.9.0.pre
83 changes: 44 additions & 39 deletions lib/mongomatic/base.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ class Base
include Mongomatic::Util
include Mongomatic::ActiveModelCompliancy
include Mongomatic::TypedFields

class << self
# Returns this models own db attribute if set, otherwise will return Mongomatic.db
def db
@db || Mongomatic.db || raise(ArgumentError, "No db supplied")
end

# Override Mongomatic.db with a Mongo::DB instance for this model specifically
# MyModel.db = Mongo::Connection.new().db('mydb_mymodel')
def db=(obj)
Expand All @@ -33,33 +33,38 @@ def collection
def find(query={}, opts={})
Mongomatic::Cursor.new(self, collection.find(query, opts))
end


# Query MongoDB for existing document. If found, return existing or initialize a new object with the parameters
def find_or_initialize(query={}, opts={})
find_one(query, opts) || new(query, true)
end

# Query MongoDB and return one document only. Same arguments as http://api.mongodb.org/ruby/current/Mongo/Collection.html#find_one-instance_method
def find_one(query={}, opts={})
return nil unless doc = self.collection.find_one(query, opts)
self.new(doc, false)
end

# Return a Mongomatic::Cursor instance of all documents in the collection.
def all
find
end

# Iterate over all documents in the collection (uses a Mongomatic::Cursor)
def each
find.each { |found| yield(found) }
end

# Return the first document in the collection
def first
find.limit(1).next_document
end

# Is the collection empty? This method is much more efficient than doing Collection.count == 0
def empty?
find.limit(1).has_next? == false
end

# Return the number of documents in the collection
def count
find.count
Expand All @@ -70,17 +75,17 @@ def drop
collection.drop
do_callback(:after_drop)
end

def do_callback(meth)
return false unless respond_to?(meth, true)
send(meth)
end

def insert(doc_hash, opts={})
d = new(doc_hash)
d.insert(opts)
end

def insert!(doc_hash, opts={})
insert(doc_hash, opts.merge(:safe => true))
end
Expand All @@ -95,16 +100,16 @@ def initialize(doc_hash=Mongomatic::MHash.new, is_new=true)
self.errors = Mongomatic::Errors.new
do_callback(:after_initialize)
end

def doc=(hash)
hash = Mongomatic::MHash.new(hash) unless hash.is_a?(Mongomatic::MHash)
@doc = hash
end

def doc
@doc
end

# Override this with your own validate() method for validations.
# Simply push your errors into the self.errors property and
# if self.errors remains empty your document will be valid.
Expand All @@ -114,7 +119,7 @@ def doc
def validate
true
end

def valid?
check_typed_fields!
self.errors = Mongomatic::Errors.new
Expand All @@ -123,38 +128,38 @@ def valid?
do_callback(:after_validate)
self.errors.empty?
end

def is_new?
self.is_new == true
end


def new?
self.is_new == true
end


def is_new?
!!new?
end

# Set a field on this document:
# mydoc["name"] = "Ben"
# mydoc["address"] = { "city" => "San Francisco" }
def []=(k,v)
@doc[k.to_s] = v
end

# Returns true if document contains key
def has_key?(key)
field, hash = hash_for_field(key.to_s, true)
hash.has_key?(field)
end

def set_value_for_key(key, value)
field, hash = hash_for_field(key.to_s)
hash[field] = value
end

def value_for_key(key)
field, hash = hash_for_field(key.to_s, true)
hash[field]
end

##
# Same as Hash#delete
#
Expand All @@ -172,18 +177,18 @@ def delete(key)
def [](k)
@doc[k.to_s]
end

# Merge this document with the supplied hash. Useful for updates:
# mydoc.merge(params[:user])
def merge(hash)
hash.each { |k,v| self[k] = v }; @doc
end

# Will return true if the document has been removed.
def removed?
self.removed == true
end

# Check equality with another Mongomatic document
def ==(obj)
obj.is_a?(self.class) && obj.doc["_id"] == @doc["_id"]
Expand Down Expand Up @@ -222,7 +227,7 @@ def insert(opts={})
do_callback(:after_insert_or_update)
ret
end

# Calls insert(...) with {:safe => true} passed in as an option.
# * Raises Mongo::OperationError if there was a DB error on inserting
# If you want to raise the following errors also, pass in {:raise => true}
Expand All @@ -231,7 +236,7 @@ def insert(opts={})
def insert!(opts={})
insert(opts.merge(:safe => true))
end

# Will persist any changes you have made to the document. Silently fails on
# db update error. Use update! or pass in {:safe => true} to raise a
# Mongo::OperationError if that's what you want.
Expand All @@ -254,7 +259,7 @@ def update(opts={},update_doc=@doc)
do_callback(:after_insert_or_update)
ret
end

# Calls update(...) with {:safe => true} passed in as an option.
# * Raises Mongo::OperationError if there was a DB error on updating
# If you want to raise the following errors also, pass in {:raise => true}
Expand All @@ -264,7 +269,7 @@ def update(opts={},update_doc=@doc)
def update!(opts={},update_doc=@doc)
update(opts.merge(:safe => true),update_doc)
end

# Remove this document from the collection. Silently fails on db error,
# use remove! or pass in {:safe => true} if you want an exception raised.
# If you want to raise the following errors also, pass in {:raise => true}
Expand All @@ -284,21 +289,21 @@ def remove(opts={})
do_callback(:after_remove)
ret
end
# Calls remove(...) with {:safe => true} passed in as an option.

# Calls remove(...) with {:safe => true} passed in as an option.
# * Raises Mongo::OperationError if there was a DB error on removing
# If you want to raise the following errors also, pass in {:raise => true}
# * Raises Mongomatic::Exceptions::DocumentIsNew if document is new
# * Raises Mongomatic::Exceptions::DocumentWasRemoved if document has been already removed
def remove!(opts={})
remove(opts.merge(:safe => true))
end

# Return this document as a hash.
def to_hash
@doc || {}
end

def hash_for_field(field, break_if_dne=false)
parts = field.split(".")
curr_hash = self.doc
Expand All @@ -312,14 +317,14 @@ def hash_for_field(field, break_if_dne=false)
curr_hash = curr_hash[part_accessor]
end
end

def do_callback(meth)
notify(meth) if self.class.included_modules.include?(Mongomatic::Observable) # TODO entire block is smelly, doesnt belong here

return false unless respond_to?(meth, true)
send(meth)
end

def transaction(key=nil, duration=5, &block)
raise Mongomatic::Exceptions::DocumentIsNew if new?
if key.is_a?(Hash) && key[:scope]
Expand Down
14 changes: 14 additions & 0 deletions test/test_find.rb
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,20 @@ def test_find_one_with_id_or_hash
assert_equal found, p
end

def test_find_or_initialize_with_query
Person.collection.drop
p1 = Person.new(:name => 'Jordan')
p1.insert
assert_equal p1, Person.find_one(:name => "Jordan")

r1 = Person.find_or_initialize({:name => 'Jordan'})
assert_equal r1.is_new?, false
assert_equal r1, p1

r2 = Person.find_or_initialize({:name => 'Jason'})
assert_equal r2.is_new?, true
end

def test_limit_and_sort
Person.collection.drop
p = Person.new(:name => "Ben", :birth_year => 1984, :created_at => Time.now.utc, :admin => true)
Expand Down

0 comments on commit 04056a4

Please sign in to comment.