candy 0.1.0 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|