candy 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/HISTORY.markdown +27 -0
- data/LICENSE.markdown +110 -0
- data/README.markdown +269 -41
- data/Rakefile +7 -9
- data/VERSION +1 -1
- data/candy.gemspec +35 -21
- data/lib/candy.rb +11 -138
- data/lib/candy/array.rb +74 -0
- data/lib/candy/collection.rb +151 -0
- data/lib/candy/crunch.rb +85 -44
- data/lib/candy/embeddable.rb +34 -0
- data/lib/candy/factory.rb +30 -0
- data/lib/candy/hash.rb +30 -0
- data/lib/candy/piece.rb +210 -0
- data/lib/candy/wrapper.rb +39 -13
- data/spec/candy/array_spec.rb +50 -0
- data/spec/candy/collection_spec.rb +102 -0
- data/spec/candy/crunch_spec.rb +17 -36
- data/spec/candy/hash_spec.rb +38 -0
- data/spec/candy/piece_spec.rb +259 -0
- data/spec/candy/wrapper_spec.rb +5 -12
- data/spec/candy_spec.rb +1 -191
- data/spec/spec_helper.rb +5 -1
- data/spec/support/kitkat_fixture.rb +10 -0
- data/spec/support/zagnuts_fixture.rb +10 -0
- metadata +62 -36
- data/LICENSE +0 -20
data/Rakefile
CHANGED
@@ -5,22 +5,20 @@ begin
|
|
5
5
|
require 'jeweler'
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "candy"
|
8
|
-
gem.summary = %Q{
|
8
|
+
gem.summary = %Q{Transparent persistence for MongoDB}
|
9
9
|
gem.description = <<DESCRIPTION
|
10
|
-
Candy
|
11
|
-
|
12
|
-
and
|
13
|
-
|
14
|
-
Mongo's atomic operators are used whenever possible, and a smart serializer (Candy::Wrapper)
|
15
|
-
converts almost any object for assignment to any attribute.
|
10
|
+
Candy provides simple, transparent object persistence for the MongoDB database. Classes that
|
11
|
+
include Candy modules save all properties to Mongo automatically, can be recursively embedded,
|
12
|
+
and can retrieve records with chainable open-ended class methods, eliminating the need for
|
13
|
+
method calls like 'save' and 'find.'
|
16
14
|
DESCRIPTION
|
17
15
|
|
18
16
|
gem.email = "sfeley@gmail.com"
|
19
17
|
gem.homepage = "http://github.com/SFEley/candy"
|
20
18
|
gem.authors = ["Stephen Eley"]
|
21
|
-
gem.add_dependency "mongo", ">= 0.
|
19
|
+
gem.add_dependency "mongo", ">= 0.19.1"
|
22
20
|
gem.add_development_dependency "rspec", ">= 1.2.9"
|
23
|
-
gem.add_development_dependency "yard", ">= 0"
|
21
|
+
# gem.add_development_dependency "yard", ">= 0"
|
24
22
|
gem.add_development_dependency "mocha", ">= 0.9.8"
|
25
23
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
26
24
|
end
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1
|
1
|
+
0.2.1
|
data/candy.gemspec
CHANGED
@@ -5,53 +5,70 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{candy}
|
8
|
-
s.version = "0.1
|
8
|
+
s.version = "0.2.1"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Stephen Eley"]
|
12
|
-
s.date = %q{2010-
|
13
|
-
s.description = %q{Candy
|
14
|
-
|
15
|
-
and
|
16
|
-
|
17
|
-
Mongo's atomic operators are used whenever possible, and a smart serializer (Candy::Wrapper)
|
18
|
-
converts almost any object for assignment to any attribute.
|
12
|
+
s.date = %q{2010-04-05}
|
13
|
+
s.description = %q{Candy provides simple, transparent object persistence for the MongoDB database. Classes that
|
14
|
+
include Candy modules save all properties to Mongo automatically, can be recursively embedded,
|
15
|
+
and can retrieve records with chainable open-ended class methods, eliminating the need for
|
16
|
+
method calls like 'save' and 'find.'
|
19
17
|
}
|
20
18
|
s.email = %q{sfeley@gmail.com}
|
21
19
|
s.extra_rdoc_files = [
|
22
|
-
"LICENSE",
|
20
|
+
"LICENSE.markdown",
|
23
21
|
"README.markdown"
|
24
22
|
]
|
25
23
|
s.files = [
|
26
24
|
".document",
|
27
25
|
".gitignore",
|
28
|
-
"
|
26
|
+
"HISTORY.markdown",
|
27
|
+
"LICENSE.markdown",
|
29
28
|
"README.markdown",
|
30
29
|
"Rakefile",
|
31
30
|
"VERSION",
|
32
31
|
"candy.gemspec",
|
33
32
|
"lib/candy.rb",
|
33
|
+
"lib/candy/array.rb",
|
34
|
+
"lib/candy/collection.rb",
|
34
35
|
"lib/candy/crunch.rb",
|
36
|
+
"lib/candy/embeddable.rb",
|
35
37
|
"lib/candy/exceptions.rb",
|
38
|
+
"lib/candy/factory.rb",
|
39
|
+
"lib/candy/hash.rb",
|
40
|
+
"lib/candy/piece.rb",
|
36
41
|
"lib/candy/qualified_const_get.rb",
|
37
42
|
"lib/candy/wrapper.rb",
|
43
|
+
"spec/candy/array_spec.rb",
|
44
|
+
"spec/candy/collection_spec.rb",
|
38
45
|
"spec/candy/crunch_spec.rb",
|
46
|
+
"spec/candy/hash_spec.rb",
|
47
|
+
"spec/candy/piece_spec.rb",
|
39
48
|
"spec/candy/wrapper_spec.rb",
|
40
49
|
"spec/candy_spec.rb",
|
41
50
|
"spec/spec.opts",
|
42
51
|
"spec/spec.watchr",
|
43
|
-
"spec/spec_helper.rb"
|
52
|
+
"spec/spec_helper.rb",
|
53
|
+
"spec/support/kitkat_fixture.rb",
|
54
|
+
"spec/support/zagnuts_fixture.rb"
|
44
55
|
]
|
45
56
|
s.homepage = %q{http://github.com/SFEley/candy}
|
46
57
|
s.rdoc_options = ["--charset=UTF-8"]
|
47
58
|
s.require_paths = ["lib"]
|
48
|
-
s.rubygems_version = %q{1.3.
|
49
|
-
s.summary = %q{
|
59
|
+
s.rubygems_version = %q{1.3.6}
|
60
|
+
s.summary = %q{Transparent persistence for MongoDB}
|
50
61
|
s.test_files = [
|
51
|
-
"spec/candy/
|
62
|
+
"spec/candy/array_spec.rb",
|
63
|
+
"spec/candy/collection_spec.rb",
|
64
|
+
"spec/candy/crunch_spec.rb",
|
65
|
+
"spec/candy/hash_spec.rb",
|
66
|
+
"spec/candy/piece_spec.rb",
|
52
67
|
"spec/candy/wrapper_spec.rb",
|
53
68
|
"spec/candy_spec.rb",
|
54
|
-
"spec/spec_helper.rb"
|
69
|
+
"spec/spec_helper.rb",
|
70
|
+
"spec/support/kitkat_fixture.rb",
|
71
|
+
"spec/support/zagnuts_fixture.rb"
|
55
72
|
]
|
56
73
|
|
57
74
|
if s.respond_to? :specification_version then
|
@@ -59,20 +76,17 @@ converts almost any object for assignment to any attribute.
|
|
59
76
|
s.specification_version = 3
|
60
77
|
|
61
78
|
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
62
|
-
s.add_runtime_dependency(%q<mongo>, [">= 0.
|
79
|
+
s.add_runtime_dependency(%q<mongo>, [">= 0.19.1"])
|
63
80
|
s.add_development_dependency(%q<rspec>, [">= 1.2.9"])
|
64
|
-
s.add_development_dependency(%q<yard>, [">= 0"])
|
65
81
|
s.add_development_dependency(%q<mocha>, [">= 0.9.8"])
|
66
82
|
else
|
67
|
-
s.add_dependency(%q<mongo>, [">= 0.
|
83
|
+
s.add_dependency(%q<mongo>, [">= 0.19.1"])
|
68
84
|
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
69
|
-
s.add_dependency(%q<yard>, [">= 0"])
|
70
85
|
s.add_dependency(%q<mocha>, [">= 0.9.8"])
|
71
86
|
end
|
72
87
|
else
|
73
|
-
s.add_dependency(%q<mongo>, [">= 0.
|
88
|
+
s.add_dependency(%q<mongo>, [">= 0.19.1"])
|
74
89
|
s.add_dependency(%q<rspec>, [">= 1.2.9"])
|
75
|
-
s.add_dependency(%q<yard>, [">= 0"])
|
76
90
|
s.add_dependency(%q<mocha>, [">= 0.9.8"])
|
77
91
|
end
|
78
92
|
end
|
data/lib/candy.rb
CHANGED
@@ -1,140 +1,13 @@
|
|
1
|
-
|
2
|
-
require 'candy/crunch'
|
3
|
-
require 'candy/wrapper'
|
1
|
+
# encoding: utf-8
|
4
2
|
|
5
|
-
#
|
6
|
-
|
7
|
-
|
8
|
-
module ClassMethods
|
9
|
-
include Crunch::ClassMethods
|
10
|
-
|
11
|
-
attr_reader :stamp_create, :stamp_update
|
12
|
-
|
13
|
-
# Retrieves an object from Mongo by its ID and returns it. Returns nil if the ID isn't found in Mongo.
|
14
|
-
def find(id)
|
15
|
-
if collection.find_one(id)
|
16
|
-
self.new({:_candy => id})
|
17
|
-
end
|
18
|
-
end
|
19
|
-
|
20
|
-
# Retrieves a single object from Mongo by its search attributes, or nil if it can't be found.
|
21
|
-
def first(conditions={})
|
22
|
-
options = extract_options(conditions)
|
23
|
-
if record = collection.find_one(conditions, options)
|
24
|
-
find(record["_id"])
|
25
|
-
else
|
26
|
-
nil
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
# Retrieves all objects matching the search attributes as an Enumerator (even if it's an empty one).
|
31
|
-
# The option fields from Mongo::Collection#find can be passed as well, and will be extracted from the
|
32
|
-
# condition set if they're found.
|
33
|
-
def all(conditions={})
|
34
|
-
options = extract_options(conditions)
|
35
|
-
cursor = collection.find(conditions, options)
|
36
|
-
Enumerator.new do |yielder|
|
37
|
-
while record = cursor.next_document do
|
38
|
-
yielder.yield find(record["_id"])
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# Configures objects to set `created_at` and `updated_at` properties at the appropriate times.
|
44
|
-
# Pass `:create` or `:update` to limit it to just one or the other. Defaults to both.
|
45
|
-
def timestamp(*args)
|
46
|
-
args = [:create, :update] if args.empty?
|
47
|
-
@stamp_create = args.include?(:create)
|
48
|
-
@stamp_update = args.include?(:update)
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
# Returns a hash of options matching those enabled in Mongo::Collection#find, if any of them exist
|
53
|
-
# in the set of search conditions.
|
54
|
-
def extract_options(conditions)
|
55
|
-
options = {:fields => []}
|
56
|
-
[:fields, :skip, :limit, :sort, :hint, :snapshot, :timeout].each do |option|
|
57
|
-
options[option] = conditions.delete(option) if conditions[option]
|
58
|
-
end
|
59
|
-
options
|
60
|
-
end
|
61
|
-
|
62
|
-
end
|
63
|
-
|
64
|
-
module InstanceMethods
|
65
|
-
include Crunch::InstanceMethods
|
66
|
-
|
67
|
-
# We push ourselves into the DB before going on with our day.
|
68
|
-
def initialize(*args, &block)
|
69
|
-
@__candy = check_for_candy(args) ||
|
70
|
-
self.class.collection.insert(self.class.stamp_create ? {:created_at => Time.now.utc} : {})
|
71
|
-
super
|
72
|
-
end
|
3
|
+
# Make me one with everything...
|
4
|
+
Dir[File.join(File.dirname(__FILE__), 'candy', '*.rb')].each {|f| require f}
|
73
5
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
if name =~ /(.*)=$/ # We're assigning
|
83
|
-
set $1, Wrapper.wrap(args[0])
|
84
|
-
else
|
85
|
-
Wrapper.unwrap(self.class.collection.find_one(@__candy, :fields => [name.to_s])[name.to_s])
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
# Given either a property/value pair or a hash (which can contain several property/value pairs), sets those
|
90
|
-
# values in Mongo using the atomic $set. The first form is functionally equivalent to simply using the
|
91
|
-
# magic assignment operator; i.e., `me.set(:foo, 'bar')` is the same as `me.foo = bar`.
|
92
|
-
def set(*args)
|
93
|
-
if args.length > 1 # This is the property/value form
|
94
|
-
hash = {args[0] => args[1]}
|
95
|
-
else
|
96
|
-
hash = args[0]
|
97
|
-
end
|
98
|
-
hash.merge!(:updated_at => Time.now.utc) if self.class.stamp_update
|
99
|
-
update '$set' => hash
|
100
|
-
end
|
101
|
-
|
102
|
-
# Given a Candy array property, appends a value or values to the end of that array using the atomic $push.
|
103
|
-
# (Note that we don't actually check the property to make sure it's an array and $push is valid. If it isn't,
|
104
|
-
# this operation will silently fail.)
|
105
|
-
def push(property, *values)
|
106
|
-
values.each do |value|
|
107
|
-
update '$push' => {property => Wrapper.wrap(value)}
|
108
|
-
end
|
109
|
-
end
|
110
|
-
|
111
|
-
# Given a Candy integer property, increments it by the given value (which defaults to 1) using the atomic $inc.
|
112
|
-
# (Note that we don't actually check the property to make sure it's an integer and $inc is valid. If it isn't,
|
113
|
-
# this operation will silently fail.)
|
114
|
-
def inc(property, increment=1)
|
115
|
-
update '$inc' => {property => increment}
|
116
|
-
end
|
117
|
-
private
|
118
|
-
|
119
|
-
# Returns the secret decoder ring buried in the arguments to "new"
|
120
|
-
def check_for_candy(args)
|
121
|
-
if (candidate = args.pop).is_a?(Hash) and candidate[:_candy]
|
122
|
-
candidate[:_candy]
|
123
|
-
else # This must not be for us, so put it back
|
124
|
-
args.push candidate if candidate
|
125
|
-
nil
|
126
|
-
end
|
127
|
-
end
|
128
|
-
|
129
|
-
# Updates the Mongo document with the given element or elements.
|
130
|
-
def update(element)
|
131
|
-
self.class.collection.update({"_id" => @__candy}, element)
|
132
|
-
end
|
133
|
-
|
134
|
-
end
|
135
|
-
|
136
|
-
def self.included(receiver)
|
137
|
-
receiver.extend ClassMethods
|
138
|
-
receiver.send :include, InstanceMethods
|
139
|
-
end
|
140
|
-
end
|
6
|
+
# Special keys for Candy metadata in the Mongo store. We try to keep these to a minimum,
|
7
|
+
# and we're using moderately obscure Unicode symbols to reduce the odds of collisions.
|
8
|
+
# If by some strange happenstance you might have single-character keys in your Mongo
|
9
|
+
# collections that use the 'CIRCLED LATIN SMALL LETTER' Unicode set, you may need to
|
10
|
+
# change these constants. Just be consistent about it if you want to use embedded
|
11
|
+
# documents in Candy.
|
12
|
+
CLASS_KEY = 'ⓒ'.to_sym
|
13
|
+
EMBED_KEY = 'ⓔ'.to_sym
|
data/lib/candy/array.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'candy/crunch'
|
2
|
+
require 'candy/embeddable'
|
3
|
+
|
4
|
+
module Candy
|
5
|
+
|
6
|
+
# An array-like object that saves itself to a parent Candy::Piece object. MongoDB's atomic
|
7
|
+
# array operators are used extensively to perform concurrency-friendly updates of individual
|
8
|
+
# array elements without rewriting the whole array.
|
9
|
+
class CandyArray
|
10
|
+
include Crunch
|
11
|
+
include Embeddable
|
12
|
+
|
13
|
+
# Included for purposes of 'embeddable' compatbility, but does nothing except pass its
|
14
|
+
# parameters to the new object. Since you can't save an array on its own anyway, there's
|
15
|
+
# no need to flag it as "don't save."
|
16
|
+
def self.embed(*args, &block)
|
17
|
+
self.new(*args, &block)
|
18
|
+
end
|
19
|
+
|
20
|
+
# Sets the initial array state.
|
21
|
+
def initialize(*args, &block)
|
22
|
+
@__candy = args
|
23
|
+
end
|
24
|
+
|
25
|
+
# Set a value at a specified index in our array. Note that this operation _does not_ confirm that the
|
26
|
+
# array in Mongo still matches our current state. If concurrent updates have happened, you might end up
|
27
|
+
# overwriting something other than what you thought.
|
28
|
+
def []=(index, val)
|
29
|
+
property = embeddify(val)
|
30
|
+
@__candy_parent.set embedded(index => property)
|
31
|
+
self.candy[index] = property
|
32
|
+
end
|
33
|
+
|
34
|
+
# Retrieves the value from our internal array.
|
35
|
+
def [](index)
|
36
|
+
candy[index]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Appends a value to our array.
|
40
|
+
def <<(val)
|
41
|
+
property = embeddify(val)
|
42
|
+
@__candy_parent.operate :push, @__candy_parent_key => property
|
43
|
+
self.candy << property
|
44
|
+
end
|
45
|
+
alias_method :push, :<<
|
46
|
+
|
47
|
+
# Pops the front off the MongoDB array and returns it, then resyncs the array.
|
48
|
+
# (Thus supporting real-time concurrency for queue-like behavior.)
|
49
|
+
def shift(n=1)
|
50
|
+
doc = @__candy_parent.findAndModify({"_id" => @__candy_parent.id}, {'$pop' => {@__candy_parent_key => -1}})
|
51
|
+
@__candy = doc['value'][@__candy_parent_key.to_s]
|
52
|
+
@__candy.shift
|
53
|
+
end
|
54
|
+
|
55
|
+
# Returns the array of memoized values.
|
56
|
+
def candy
|
57
|
+
@__candy ||= []
|
58
|
+
end
|
59
|
+
alias_method :to_mongo, :candy
|
60
|
+
alias_method :to_ary, :candy
|
61
|
+
|
62
|
+
# Array equality.
|
63
|
+
def ==(val)
|
64
|
+
self.to_ary == val
|
65
|
+
end
|
66
|
+
|
67
|
+
# Array length.
|
68
|
+
def length
|
69
|
+
self.to_ary.length
|
70
|
+
end
|
71
|
+
alias_method :size, :length
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
require 'candy/crunch'
|
2
|
+
require 'candy/hash'
|
3
|
+
|
4
|
+
module Candy
|
5
|
+
|
6
|
+
# Handles Mongo queries for cursors upon a particular Mongo collection.
|
7
|
+
module Collection
|
8
|
+
FIND_OPTIONS = [:fields, :skip, :limit, :sort, :hint, :snapshot, :timeout]
|
9
|
+
UP_SORTS = [Mongo::ASCENDING, 'ascending', 'asc', :ascending, :asc, 1, :up]
|
10
|
+
DOWN_SORTS = [Mongo::DESCENDING, 'descending', 'desc', :descending, :desc, -1, :down]
|
11
|
+
|
12
|
+
include Enumerable
|
13
|
+
|
14
|
+
module ClassMethods
|
15
|
+
include Crunch::ClassMethods
|
16
|
+
|
17
|
+
attr_reader :_candy_piece
|
18
|
+
|
19
|
+
# Sets the collection that all queries run against, qualified by
|
20
|
+
# the namespace of the current class. (I.e., if this class is
|
21
|
+
# BigModule::LittleModule::People, `collects :person` will look for
|
22
|
+
# a collection named "BigModule::LittleModule::Person".) You can also
|
23
|
+
# specify a class that includes Candy::Piece that will be instantiated
|
24
|
+
# for all found records. Otherwise the collection name is used as a
|
25
|
+
# default, and CandyHash is a fallback.
|
26
|
+
def collects(collection, piece = nil)
|
27
|
+
collectible = namespace + camelcase(collection)
|
28
|
+
piecemeal = (piece ? namespace + camelcase(piece) : collectible)
|
29
|
+
self.collection = collectible
|
30
|
+
@_candy_piece = Kernel.qualified_const_get(piecemeal) || CandyHash
|
31
|
+
end
|
32
|
+
|
33
|
+
def all(options={})
|
34
|
+
self.new(options)
|
35
|
+
end
|
36
|
+
|
37
|
+
def method_missing(name, *args, &block)
|
38
|
+
coll = self.new
|
39
|
+
coll.send(name, *args, &block)
|
40
|
+
end
|
41
|
+
private
|
42
|
+
# Retrieves the 'BlahModule::BleeModule::' part of the class name, so that we
|
43
|
+
# can put other things in the same namespace.
|
44
|
+
def namespace
|
45
|
+
name[/^.*::/] || '' # Hooray for greedy matching
|
46
|
+
end
|
47
|
+
|
48
|
+
# Modified from ActiveSupport (http://rubyonrails.org)
|
49
|
+
def camelcase(stringy)
|
50
|
+
stringy.to_s.gsub(/(?:^|_)(.)/) {$1.upcase}
|
51
|
+
end
|
52
|
+
|
53
|
+
# Creates a method in the same namespace as the included class that points to
|
54
|
+
# 'all', for easier semantics.
|
55
|
+
def self.extended(receiver)
|
56
|
+
Factory.magic_method(receiver, 'all', 'conditions={}')
|
57
|
+
end
|
58
|
+
|
59
|
+
end # Here endeth the ClassMethods module
|
60
|
+
|
61
|
+
def initialize(*args, &block)
|
62
|
+
conditions = args.pop || {}
|
63
|
+
super
|
64
|
+
@_candy_query = {}
|
65
|
+
if conditions.is_a?(Hash)
|
66
|
+
@_candy_options = {:fields => '_id'}.merge(extract_options(conditions))
|
67
|
+
@_candy_query.merge!(conditions)
|
68
|
+
else
|
69
|
+
@_candy_options = {:fields => '_id'}
|
70
|
+
end
|
71
|
+
refresh_cursor
|
72
|
+
end
|
73
|
+
|
74
|
+
def method_missing(name, *args, &block)
|
75
|
+
if @_candy_cursor.respond_to?(name)
|
76
|
+
@_candy_cursor.send(name, *args, &block)
|
77
|
+
elsif FIND_OPTIONS.include?(name)
|
78
|
+
@_candy_options[name] = args.shift
|
79
|
+
refresh_cursor
|
80
|
+
self
|
81
|
+
else
|
82
|
+
@_candy_query[name] = args.shift
|
83
|
+
@_candy_query.merge!(args) if args.is_a?(Hash)
|
84
|
+
refresh_cursor
|
85
|
+
self
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
|
90
|
+
# Makes our collection enumerable. This relies heavily on Mongo::Cursor methods --
|
91
|
+
# we only reimplement it so that the objects we return can be Candy objects.
|
92
|
+
def each
|
93
|
+
while this = @_candy_cursor.next_document
|
94
|
+
yield self.class._candy_piece.new(this)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Get our next document as a Candy object, if there is one.
|
99
|
+
def next
|
100
|
+
if this = @_candy_cursor.next_document
|
101
|
+
self.class._candy_piece.new(this)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Determines the sort order for this collection, with somewhat simpler semantics than
|
106
|
+
# the MongoDB options. Each value is either a field name (which defaults to ascending sort)
|
107
|
+
# or a direction (which modifies the field name immediately prior) or a two-element array of
|
108
|
+
# same. Direction can be :up or :down in addition to Mongo's accepted values of :ascending,
|
109
|
+
# 'asc', 1, etc.
|
110
|
+
#
|
111
|
+
# As an added bonus, sorts can also be chained. If you call #sort more than once then all
|
112
|
+
# sorts will be applied in the order in which you called them.
|
113
|
+
def sort(*fields)
|
114
|
+
@_candy_sort ||= []
|
115
|
+
temp_sort = []
|
116
|
+
until fields.flatten.empty?
|
117
|
+
this = fields.pop # We're going backwards so that we can test for modifiers
|
118
|
+
if UP_SORTS.include?(this)
|
119
|
+
temp_sort.unshift [fields.pop, Mongo::ASCENDING]
|
120
|
+
elsif DOWN_SORTS.include?(this)
|
121
|
+
temp_sort.unshift [fields.pop, Mongo::DESCENDING]
|
122
|
+
else
|
123
|
+
temp_sort.unshift [this, Mongo::ASCENDING]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
@_candy_sort += temp_sort
|
127
|
+
@_candy_cursor.sort(@_candy_sort)
|
128
|
+
self
|
129
|
+
end
|
130
|
+
|
131
|
+
|
132
|
+
private
|
133
|
+
|
134
|
+
def refresh_cursor
|
135
|
+
@_candy_cursor = self.class.collection.find(@_candy_query, @_candy_options)
|
136
|
+
end
|
137
|
+
|
138
|
+
def extract_options(hash)
|
139
|
+
options = {}
|
140
|
+
(FIND_OPTIONS & hash.keys).each do |key|
|
141
|
+
options[key] = hash.delete(key)
|
142
|
+
end
|
143
|
+
options
|
144
|
+
end
|
145
|
+
|
146
|
+
def self.included(receiver)
|
147
|
+
receiver.extend ClassMethods
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|