Permalink
Browse files

Merge remote branch 'sfeley/master'

  • Loading branch information...
2 parents fbbd603 + 28eb0c5 commit 0ea973d4021d734f4c98fff2ffbf47f215c4f98a @paulcbetts committed Jul 6, 2010
View
@@ -3,6 +3,52 @@ Candy History
This document aims to provide only an overview. Further, we've only really been tracking things since **v0.2**. For obsessive detail, just check out the `git log`.
+v0.2.10 - 2010-06-10 (the "This is not my beautiful hash" release)
+------------------------------------------------------------------
+Made arrays enumerable finally (thanks to dominikh with issue #13) and added some vital hash methods to Piece that I needed.
+
+**NOTE:** I've been slow on updates the past few weeks. It's because I got a crazy new idea on how to interface with the Mongo parts (the "Candy::Crunch" part of this gem) and I've been spending my free dev time playing with that. It might take a bit longer, but if I can get it to work, the non-driver parts of Candy will be both simpler and more incredible. Ping me if you want to know more about what I'm babbling about.
+
+* Fixed Github issue #13
+* Added Piece#keys and Piece#values
+
+
+v0.2.9 - 2010-05-14 (the "+1" release)
+--------------------------------------
+Moved methods around again, placing more of the database update methods into Candy::Crunch. Also began support for two flavors of
+atomic update methods:
+
+1. _Safe_ methods that call the collection in "safe" mode, meaning that it's much slower but the update is verified and exceptions are returned by the Mongo driver. These methods also return the new values. So far supported are **set** and **inc**.
+2. _Unsafe_ or _bang!_ methods that call the collection in "unsafe" mode, for maximum speed but without verification or new values returned. So far supported are **set!** and **inc!**.
+
+* Refactored; added 'bang!' methods for atomic updates
+
+
+v0.2.8 - 2010-05-13 (the "Holy crap, that was ten pomodoros" release)
+---------------------------------------------------------------------
+Major refactoring to fix a major bug: embedded documents weren't being loaded properly on document retrieval. This resulted in a lot of code being moved around, and some regrettable circular connascence between Piece and Wrapper that I hope to address later. Overall, though, it's simpler now.
+
+**API CHANGE:** The `.embed` class method has two new required parameters and is now used _only_ when you know the parent you want to embed something in. To make a Candy piece that you don't want to be saved right away, use `.piece` instead.
+
+* Fixed Github issue #11
+
+v0.2.7 - 2010-05-05 (the "yes, you MAY put your peanut butter in my chocolate" release)
+--------------------------------------------------------------------------------------
+Found and fixed a convoluted bug that was preventing embedded Candy objects from being saved properly. (It was treating them as _non_-Candy objects, which makes the world a gray and boring place.) While I was at it, refactored some methods and chipped away at some complexity.
+
+**MODERATELY BREAKING CHANGE ALERT:** I've renamed the `to_mongo` and `from_mongo` methods to `to_candy` and `from_candy`. The initial reason for the _mongo_ names was for some vague semblance of compatibility with [MongoMapper](http://github.com/jnunemaker/mongomapper), but that doesn't make sense since we're treating serialized Candy objects completely differently and expecting them to unpack themselves. I seriously doubt anyone was using these methods yet, but just in case, now you know.
+
+* Fixed embedding bug on Candy objects
+
+v0.2.6 - 2010-05-03 (the "Spanish Fly" release)
+-----------------------------------------------
+Thanks to [xpaulbettsx](http://github.com/xpaulbettsx) for pointing out in issue \#4 that Candy was attempting to connect to localhost prematurely.
+A stray setting of the collection name in CandyHash was the culprit, causing a cascade of lookups. Refactored to maintain lazy evaluation of the whole MongoDB object chain, and while I was at it, moved most of the interesting objects into `autoload` conditions in the main Candy file instead of `require`.
+
+* Reorganized for autoloading
+* Fixed issue #4 - tries to connect to host immediately on require
+
+
v0.2.5 - 2010-05-02 (the "John::Jacob::Jingleheimer::Schmidt" release)
----------------------------------------------------------------------
As I was building an app based on several Sinatra building blocks, I realized that Candy was creating collection names like **Login::Person** and **Profile::Person** with complete module namespaces. I wanted both of those **Person** classes to be different views on the same data, and having to override the collection name each time was becoming a pain. I'm not sure that fully namespacing the collection names inside Mongo has much value, and we weren't really documenting that it was happening, so I've simplified things.
View
@@ -29,7 +29,7 @@ We got 'em. Candy pieces can contain each other recursively, to any arbitrary d
seafood: 'Maryland blue crabs',
scotch: ['Glenmorangie Port Wood Finish',
'Balvenie Single Barrel']}
- me.spouse = Person.embed(first_name: 'Anna', eyes: :blue)
+ me.spouse = Person.piece(first_name: 'Anna', eyes: :blue)
me.spouse.eyes # => :blue
me.favorites.scotch[1] # => 'Balvenie Single Barrel'
View
@@ -1 +1 @@
-0.2.5
+0.2.10
View
@@ -5,11 +5,11 @@
Gem::Specification.new do |s|
s.name = %q{candy}
- s.version = "0.2.5"
+ s.version = "0.2.10"
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["Stephen Eley"]
- s.date = %q{2010-05-03}
+ s.date = %q{2010-06-10}
s.description = %q{Candy provides simple, transparent object persistence for the MongoDB database. Classes that
include Candy modules save all properties to Mongo automatically, can be recursively embedded,
and can retrieve records with chainable open-ended class methods, eliminating the need for
@@ -33,6 +33,7 @@ method calls like 'save' and 'find.'
"lib/candy/array.rb",
"lib/candy/collection.rb",
"lib/candy/crunch.rb",
+ "lib/candy/crunch/document.rb",
"lib/candy/embeddable.rb",
"lib/candy/exceptions.rb",
"lib/candy/factory.rb",
@@ -56,7 +57,7 @@ method calls like 'save' and 'find.'
s.homepage = %q{http://github.com/SFEley/candy}
s.rdoc_options = ["--charset=UTF-8"]
s.require_paths = ["lib"]
- s.rubygems_version = %q{1.3.6}
+ s.rubygems_version = %q{1.3.7}
s.summary = %q{Transparent persistence for MongoDB}
s.test_files = [
"spec/candy/array_spec.rb",
@@ -75,7 +76,7 @@ method calls like 'save' and 'find.'
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
s.specification_version = 3
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
s.add_runtime_dependency(%q<bson>, [">= 0.20.1"])
s.add_runtime_dependency(%q<bson_ext>, [">= 0.20.1"])
s.add_runtime_dependency(%q<mongo>, [">= 0.20.1"])
View
@@ -1,13 +1,24 @@
# encoding: utf-8
-# Make me one with everything...
-Dir[File.join(File.dirname(__FILE__), 'candy', '*.rb')].each {|f| require f}
+require 'candy/crunch'
+require 'candy/exceptions'
+
+module Candy
+ # Let's be minimalist here. Some implementations may not need Collections, or Arrays, etc.
+ # Anything not in the autoload list below is unlikely to be accessed directly by an end user.
+ autoload :CandyHash, 'candy/hash'
+ autoload :CandyArray, 'candy/array'
+ autoload :Wrapper, 'candy/wrapper'
+ autoload :Piece, 'candy/piece'
+ autoload :Collection, 'candy/collection'
+
+ # Special keys for Candy metadata in the Mongo store. We try to keep these to a minimum,
+ # and we're using moderately obscure Unicode symbols to reduce the odds of collisions.
+ # If by some strange happenstance you might have single-character keys in your Mongo
+ # collections that use the 'CIRCLED LATIN SMALL LETTER' Unicode set, you may need to
+ # change these constants. Just be consistent about it if you want to use embedded
+ # documents in Candy.
+ CLASS_KEY = ''.to_sym
+ EMBED_KEY = ''.to_sym
+end
-# Special keys for Candy metadata in the Mongo store. We try to keep these to a minimum,
-# and we're using moderately obscure Unicode symbols to reduce the odds of collisions.
-# If by some strange happenstance you might have single-character keys in your Mongo
-# collections that use the 'CIRCLED LATIN SMALL LETTER' Unicode set, you may need to
-# change these constants. Just be consistent about it if you want to use embedded
-# documents in Candy.
-CLASS_KEY = ''.to_sym
-EMBED_KEY = ''.to_sym
View
@@ -9,24 +9,25 @@ module Candy
class CandyArray
include Crunch
include Embeddable
+ include Enumerable
- # Included for purposes of 'embeddable' compatbility, but does nothing except pass its
- # parameters to the new object. Since you can't save an array on its own anyway, there's
- # no need to flag it as "don't save."
- def self.embed(*args, &block)
- self.new(*args, &block)
+ # Creates the object with parent and attribute values set properly on the object and any children.
+ def self.embed(parent, attribute, *args)
+ this = self.new(*args)
+ this.candy_adopt(parent, attribute)
end
# Sets the initial array state.
- def initialize(*args, &block)
- @__candy = args
+ def initialize(*args)
+ @__candy = from_candy(args)
+ super()
end
# Set a value at a specified index in our array. Note that this operation _does not_ confirm that the
# array in Mongo still matches our current state. If concurrent updates have happened, you might end up
# overwriting something other than what you thought.
def []=(index, val)
- property = embeddify(val)
+ property = candy_coat(nil, val) # There are no attribute names on array inheritance
@__candy_parent.set embedded(index => property)
self.candy[index] = property
end
@@ -36,9 +37,14 @@ def [](index)
candy[index]
end
+ # Iterates over each value in turn, so that we can have proper Enumerable support
+ def each(&block)
+ candy.each(&block)
+ end
+
# Appends a value to our array.
def <<(val)
- property = embeddify(val)
+ property = candy_coat(@__candy_parent_key, val)
@__candy_parent.operate :push, @__candy_parent_key => property
self.candy << property
end
@@ -47,18 +53,23 @@ def <<(val)
# Pops the front off the MongoDB array and returns it, then resyncs the array.
# (Thus supporting real-time concurrency for queue-like behavior.)
def shift(n=1)
- doc = @__candy_parent.findAndModify({"_id" => @__candy_parent.id}, {'$pop' => {@__candy_parent_key => -1}})
- @__candy = doc['value'][@__candy_parent_key.to_s]
+ doc = @__candy_parent.collection.find_and_modify query: {"_id" => @__candy_parent.id}, update: {'$pop' => {@__candy_parent_key => -1}}, new: false
+ @__candy = from_candy(doc[@__candy_parent_key.to_s])
@__candy.shift
end
# Returns the array of memoized values.
def candy
@__candy ||= []
end
- alias_method :to_mongo, :candy
+ alias_method :to_candy, :candy
alias_method :to_ary, :candy
+ # Unwraps all elements of the array, telling them who their parent is. The attribute doesn't matter because arrays don't have them.
+ def from_candy(array)
+ array.map {|element| Wrapper.unwrap(element, self)}
+ end
+
# Array equality.
def ==(val)
self.to_ary == val
@@ -70,5 +81,11 @@ def length
end
alias_method :size, :length
+ protected
+
+ # Sets the array. Primarily used by the .embed class method.
+ def candy=(val)
+ @__candy = val
+ end
end
end
View
@@ -96,6 +96,8 @@ def self.password
# All of the hard crunchy bits that connect us to a collection within a Mongo database.
module Crunch
+ autoload :Document, 'candy/crunch/document'
+
module ClassMethods
# Returns the connection you gave, or uses the application-level Candy collection.
@@ -163,7 +165,7 @@ def collection=(val)
when Mongo::Collection
@collection = val
when String
- @collection = db.collection(val)
+ @collection_name = val # Don't collapse the probability wave until called upon
when nil
@collection = nil
else
@@ -174,7 +176,7 @@ def collection=(val)
# Returns the collection you gave, or creates a default collection named for the current class.
# (By which we mean _just_ the class name, not the full module namespace.)
def collection
- @collection ||= db.collection(name.sub(/^.*::/,''))
+ @collection ||= db.collection(collection_name)
end
# Creates an index on the specified property, with an optional direction specified as either :asc or :desc.
@@ -191,6 +193,15 @@ def index(property, direction=:asc)
end
private
+ # If we were passed a string on #collection= then we want to pass it to the DB when the collection is referenced,
+ # not right away. So store it and pass it back on the initial call to #collection. If nothing, pass the
+ # class name instead.
+ def collection_name
+ collection_name = @collection_name || name.sub(/^.*::/,'')
+ @collection_name = nil # Forget this name to avoid confusion on subsequent calls
+ collection_name
+ end
+
# If we don't have a username AND password, returns the DB given. If we do, returns the DB if-and-only-if
# we can authenticate on that DB.
def maybe_authenticate(db)
@@ -203,18 +214,22 @@ def maybe_authenticate(db)
end
+ # HERE BEGINNETH THE MODULE PROPER.
+ # (The above were class methods.)
- # We're implementing FindAndModify on Mongo 1.4 until the Ruby driver gets around to being updated...
- def findAndModify(query, update, sort={})
- command = OrderedHash[
- findandmodify: self.collection.name,
- query: query,
- update: update,
- sort: sort
- ]
- result = self.class.db.command(command)
+ # The MongoDB collection object that everything saves to. Defaults to the class's
+ # collection, which in turn defaults to the classname.
+ def collection
+ @__candy_collection ||= self.class.collection
end
-
+
+ # This is normally set at the class level (with a default of the classname) but you
+ # can override it on a per-object basis if you need to.
+ def collection=(val)
+ @__candy_collection = val
+ end
+
+
def self.included(receiver)
receiver.extend ClassMethods
@@ -0,0 +1,62 @@
+module Candy
+ module Crunch
+
+ # MongoDB interface methods specific to the handling of individual documents (as opposed to collections or cursors).
+ module Document
+ ### RETRIEVAL METHODS
+ # Returns the listed fields of the document. If no fields are given, returns the whole document.
+ def retrieve(*fields)
+ options = (fields.empty? ? {} : {fields: fields})
+ from_candy(collection.find_one({'_id' => id}, options)) if id
+ end
+
+
+ # A generic updater that performs the atomic operation specified on a value nested arbitrarily deeply.
+ # Operates in "unsafe" mode, meaning that no document errors will be returned and results are not
+ # guaranteed. The benefit is that it's very, very fast. Always returns true.
+ def operate!(operator, fields)
+ operate operator, fields, {safe: false} and true
+ end
+
+ # A generic updater that performs the atomic operation specified on a value nested arbitrarily deeply.
+ #
+ def operate(operator, fields, options={safe: true})
+ if @__candy_parent
+ @__candy_parent.operate operator, embedded(fields), options
+ else
+ @__candy_id = collection.insert({}) unless id # Ensure we have something to update
+ collection.update({'_id' => id}, {"$#{operator}" => Wrapper.wrap(fields)}, options)
+ end
+ end
+
+ # Given a hash of property/value pairs, sets those values in Mongo using the atomic $set if
+ # we have a document ID. Otherwise inserts them and sets the object's ID. Operates in
+ # 'unsafe' mode, so database exceptions are not reported but updates are very fast.
+ def set!(fields)
+ operate! :set, fields
+ end
+
+ # Given a hash of property/value pairs, sets those values in Mongo using the atomic $set if
+ # we have a document ID. Otherwise inserts them and sets the object's ID. Returns the
+ # values passed to it.
+ def set(fields)
+ operate :set, fields
+ fields
+ end
+
+ # Increments the specified field by the specified amount (defaults to 1). Does not return the
+ # new value or any document errors.
+ def inc!(field, value=1)
+ operate! :inc, field: value
+ end
+
+ # Increments the specified field by the specified amount (defaults to 1) and returns the
+ # new value.
+ def inc(field, value=1)
+ operate :inc, field => value
+ retrieve(field)[field]
+ end
+
+ end
+ end
+end
Oops, something went wrong.

0 comments on commit 0ea973d

Please sign in to comment.