mongo_doc_rails2 0.6.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/.document +5 -0
- data/.gitignore +8 -0
- data/HISTORY.md +11 -0
- data/LICENSE +20 -0
- data/README.textile +185 -0
- data/Rakefile +188 -0
- data/TODO +40 -0
- data/VERSION +1 -0
- data/data/.gitignore +2 -0
- data/examples/simple_document.rb +46 -0
- data/examples/simple_object.rb +34 -0
- data/features/collections.feature +9 -0
- data/features/embed_hash.feature +16 -0
- data/features/finders.feature +76 -0
- data/features/indexes.feature +28 -0
- data/features/mongodb.yml +7 -0
- data/features/mongodoc_base.feature +128 -0
- data/features/new_record.feature +36 -0
- data/features/partial_updates.feature +95 -0
- data/features/removing_documents.feature +68 -0
- data/features/saving_an_object.feature +15 -0
- data/features/scopes.feature +66 -0
- data/features/step_definitions/collection_steps.rb +17 -0
- data/features/step_definitions/document_steps.rb +149 -0
- data/features/step_definitions/documents.rb +40 -0
- data/features/step_definitions/embed_hash_steps.rb +6 -0
- data/features/step_definitions/finder_steps.rb +15 -0
- data/features/step_definitions/index_steps.rb +10 -0
- data/features/step_definitions/json_steps.rb +9 -0
- data/features/step_definitions/object_steps.rb +50 -0
- data/features/step_definitions/objects.rb +24 -0
- data/features/step_definitions/partial_update_steps.rb +31 -0
- data/features/step_definitions/query_steps.rb +66 -0
- data/features/step_definitions/removing_documents_steps.rb +14 -0
- data/features/step_definitions/scope_steps.rb +18 -0
- data/features/step_definitions/string_casting_steps.rb +29 -0
- data/features/step_definitions/util_steps.rb +7 -0
- data/features/string_casting.feature +10 -0
- data/features/support/support.rb +10 -0
- data/features/using_criteria.feature +142 -0
- data/lib/mongo_doc.rb +12 -0
- data/lib/mongo_doc/associations.rb +109 -0
- data/lib/mongo_doc/associations/collection_proxy.rb +121 -0
- data/lib/mongo_doc/associations/document_proxy.rb +65 -0
- data/lib/mongo_doc/associations/hash_proxy.rb +102 -0
- data/lib/mongo_doc/associations/proxy_base.rb +48 -0
- data/lib/mongo_doc/attributes.rb +84 -0
- data/lib/mongo_doc/bson.rb +31 -0
- data/lib/mongo_doc/collection.rb +82 -0
- data/lib/mongo_doc/connection.rb +88 -0
- data/lib/mongo_doc/contexts.rb +31 -0
- data/lib/mongo_doc/contexts/ids.rb +41 -0
- data/lib/mongo_doc/contexts/mongo.rb +272 -0
- data/lib/mongo_doc/criteria.rb +70 -0
- data/lib/mongo_doc/cursor.rb +32 -0
- data/lib/mongo_doc/document.rb +205 -0
- data/lib/mongo_doc/ext.rb +16 -0
- data/lib/mongo_doc/ext/array.rb +5 -0
- data/lib/mongo_doc/ext/binary.rb +7 -0
- data/lib/mongo_doc/ext/boolean_class.rb +17 -0
- data/lib/mongo_doc/ext/date.rb +19 -0
- data/lib/mongo_doc/ext/date_time.rb +17 -0
- data/lib/mongo_doc/ext/dbref.rb +7 -0
- data/lib/mongo_doc/ext/hash.rb +7 -0
- data/lib/mongo_doc/ext/min_max_keys.rb +13 -0
- data/lib/mongo_doc/ext/nil_class.rb +5 -0
- data/lib/mongo_doc/ext/numeric.rb +17 -0
- data/lib/mongo_doc/ext/object.rb +19 -0
- data/lib/mongo_doc/ext/object_id.rb +7 -0
- data/lib/mongo_doc/ext/regexp.rb +5 -0
- data/lib/mongo_doc/ext/string.rb +5 -0
- data/lib/mongo_doc/ext/symbol.rb +5 -0
- data/lib/mongo_doc/ext/time.rb +9 -0
- data/lib/mongo_doc/finders.rb +38 -0
- data/lib/mongo_doc/index.rb +46 -0
- data/lib/mongo_doc/matchers.rb +35 -0
- data/lib/mongo_doc/root.rb +26 -0
- data/lib/mongo_doc/scope.rb +64 -0
- data/lib/mongo_doc/validations.rb +12 -0
- data/lib/mongo_doc/validations/macros.rb +11 -0
- data/lib/mongo_doc/validations/validates_embedded.rb +13 -0
- data/lib/mongoid/contexts/enumerable.rb +151 -0
- data/lib/mongoid/contexts/paging.rb +42 -0
- data/lib/mongoid/criteria.rb +239 -0
- data/lib/mongoid/criterion/complex.rb +21 -0
- data/lib/mongoid/criterion/exclusion.rb +65 -0
- data/lib/mongoid/criterion/inclusion.rb +93 -0
- data/lib/mongoid/criterion/optional.rb +136 -0
- data/lib/mongoid/extensions/hash/criteria_helpers.rb +20 -0
- data/lib/mongoid/extensions/symbol/inflections.rb +36 -0
- data/lib/mongoid/matchers/all.rb +11 -0
- data/lib/mongoid/matchers/default.rb +26 -0
- data/lib/mongoid/matchers/exists.rb +13 -0
- data/lib/mongoid/matchers/gt.rb +11 -0
- data/lib/mongoid/matchers/gte.rb +11 -0
- data/lib/mongoid/matchers/in.rb +11 -0
- data/lib/mongoid/matchers/lt.rb +11 -0
- data/lib/mongoid/matchers/lte.rb +11 -0
- data/lib/mongoid/matchers/ne.rb +11 -0
- data/lib/mongoid/matchers/nin.rb +11 -0
- data/lib/mongoid/matchers/size.rb +11 -0
- data/mongo_doc_rails2.gemspec +237 -0
- data/mongod.example.yml +2 -0
- data/mongodb.example.yml +14 -0
- data/perf/mongo_doc_object.rb +83 -0
- data/perf/mongo_document.rb +84 -0
- data/perf/ruby_driver.rb +49 -0
- data/script/console +8 -0
- data/spec/array_including_argument_matcher.rb +62 -0
- data/spec/associations/collection_proxy_spec.rb +233 -0
- data/spec/associations/document_proxy_spec.rb +45 -0
- data/spec/associations/hash_proxy_spec.rb +181 -0
- data/spec/associations/proxy_base_spec.rb +92 -0
- data/spec/associations_spec.rb +218 -0
- data/spec/attributes_accessor_spec.rb +33 -0
- data/spec/attributes_spec.rb +145 -0
- data/spec/bson_matchers.rb +54 -0
- data/spec/bson_spec.rb +196 -0
- data/spec/collection_spec.rb +169 -0
- data/spec/connection_spec.rb +147 -0
- data/spec/contexts/ids_spec.rb +49 -0
- data/spec/contexts/mongo_spec.rb +235 -0
- data/spec/contexts_spec.rb +56 -0
- data/spec/criteria_spec.rb +69 -0
- data/spec/cursor_spec.rb +91 -0
- data/spec/document_ext.rb +9 -0
- data/spec/document_spec.rb +553 -0
- data/spec/embedded_save_spec.rb +73 -0
- data/spec/ext_spec.rb +89 -0
- data/spec/finders_spec.rb +61 -0
- data/spec/hash_matchers.rb +27 -0
- data/spec/index_spec.rb +79 -0
- data/spec/matchers_spec.rb +342 -0
- data/spec/mongodb.yml +6 -0
- data/spec/mongodb_pairs.yml +8 -0
- data/spec/new_record_spec.rb +128 -0
- data/spec/root_spec.rb +41 -0
- data/spec/scope_spec.rb +79 -0
- data/spec/spec.opts +2 -0
- data/spec/spec_helper.rb +14 -0
- data/spec/validations_spec.rb +30 -0
- metadata +346 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe MongoDoc::Criteria do
|
|
4
|
+
|
|
5
|
+
class CriteriaTest
|
|
6
|
+
extend MongoDoc::Criteria
|
|
7
|
+
|
|
8
|
+
def self.collection; end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
context ".criteria" do
|
|
12
|
+
it "creates a new CriteriaWrapper for the document" do
|
|
13
|
+
CriteriaTest.criteria.should be_a_kind_of(MongoDoc::Criteria::CriteriaWrapper)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
context "CriteriaWrapper" do
|
|
18
|
+
let(:wrapper) { MongoDoc::Criteria::CriteriaWrapper.new(CriteriaTest) }
|
|
19
|
+
|
|
20
|
+
it "is a Criteria" do
|
|
21
|
+
Mongoid::Criteria.should === wrapper
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
it "sets the criteria klass" do
|
|
25
|
+
wrapper.klass.should == CriteriaTest
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
%w(all and any_in cache enslave excludes fuse in limit offset only order_by skip where).each do |wrapping_method|
|
|
29
|
+
it "#{wrapping_method} returns a new CriteriaWrapper" do
|
|
30
|
+
wrapper.send(wrapping_method).object_id.should_not == wrapper.object_id
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
it "extras returns a new CriteriaWrapper" do
|
|
35
|
+
wrapper.extras({}).object_id.should_not == wrapper.object_id
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "not_in returns a new CriteriaWrapper" do
|
|
39
|
+
wrapper.not_in({}).object_id.should_not == wrapper.object_id
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
context "criteria delegates" do
|
|
45
|
+
let(:criteria) { stub('criteria').as_null_object }
|
|
46
|
+
|
|
47
|
+
before do
|
|
48
|
+
CriteriaTest.stub(:criteria).and_return(criteria)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
%w(aggregate all and any_in blank? count empty? excludes extras first group id in last limit max min not_in offset one only order_by page paginate per_page skip sum where).each do |criteria_op|
|
|
52
|
+
it "#{criteria_op} delegates to the criteria" do
|
|
53
|
+
criteria.should_receive(criteria_op)
|
|
54
|
+
CriteriaTest.send(criteria_op)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
context "criteria are reusable" do
|
|
60
|
+
it "creates a new instance on each invocation" do
|
|
61
|
+
original = CriteriaTest.any_in(:name => 'Les Hill')
|
|
62
|
+
chained = original.only(:name)
|
|
63
|
+
original.should_not == chained
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
end
|
data/spec/cursor_spec.rb
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
|
|
2
|
+
|
|
3
|
+
describe "MongoDoc::Cursor" do
|
|
4
|
+
let(:mongo_cursor) { stub('cursor') }
|
|
5
|
+
|
|
6
|
+
let(:collection) { stub('collection') }
|
|
7
|
+
|
|
8
|
+
let(:cursor) { MongoDoc::Cursor.new(collection, mongo_cursor) }
|
|
9
|
+
|
|
10
|
+
it "is Enumerable" do
|
|
11
|
+
Enumerable.should === cursor
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
it ".new wraps a Mongo::Cursor" do
|
|
15
|
+
cursor._cursor.should == mongo_cursor
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
it "#collection returns the MongoDoc::Collection for this cursor" do
|
|
19
|
+
cursor.collection.should == collection
|
|
20
|
+
cursor._collection.should == collection
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
context "with the underlying cursor" do
|
|
24
|
+
%w(admin close closed? count explain fields full_collection_name hint limit order query_options_hash query_opts selector skip snapshot sort timeout).each do |delegated_method|
|
|
25
|
+
it "delegates #{delegated_method} to the Mongo::Cursor" do
|
|
26
|
+
mongo_cursor.should_receive(delegated_method)
|
|
27
|
+
cursor.send(delegated_method)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
context "#each" do
|
|
33
|
+
it "delegates to the cursor" do
|
|
34
|
+
mongo_cursor.should_receive(:each)
|
|
35
|
+
cursor.each
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "decodes the return from the delegate" do
|
|
39
|
+
bson = stub('bson')
|
|
40
|
+
cursor.stub(:_cursor).and_return([bson])
|
|
41
|
+
MongoDoc::BSON.should_receive(:decode).with(bson)
|
|
42
|
+
cursor.each {}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
it "calls the block with the decoded return" do
|
|
46
|
+
result = stub('bson')
|
|
47
|
+
cursor.stub(:_cursor).and_return([result])
|
|
48
|
+
MongoDoc::BSON.stub(:decode).and_return(result)
|
|
49
|
+
cursor.each {|obj| @obj = obj}
|
|
50
|
+
@obj.should == result
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
context "#next_document" do
|
|
55
|
+
it "delegates to the cursor" do
|
|
56
|
+
mongo_cursor.should_receive(:next_document)
|
|
57
|
+
cursor.next_document
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
it "decodes the return from the delegate" do
|
|
61
|
+
bson = stub('bson')
|
|
62
|
+
mongo_cursor.stub(:next_document).and_return(bson)
|
|
63
|
+
MongoDoc::BSON.should_receive(:decode).with(bson)
|
|
64
|
+
cursor.next_document
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
it "returns nil if the delegate returns nil" do
|
|
68
|
+
mongo_cursor.stub(:next_document)
|
|
69
|
+
cursor.next_document.should be_nil
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
context "#to_a" do
|
|
74
|
+
it "delegates to the cursor" do
|
|
75
|
+
mongo_cursor.should_receive(:to_a)
|
|
76
|
+
cursor.to_a
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
it "decodes the return from the delegate" do
|
|
80
|
+
array = stub('array')
|
|
81
|
+
mongo_cursor.stub(:to_a).and_return(array)
|
|
82
|
+
MongoDoc::BSON.should_receive(:decode).with(array)
|
|
83
|
+
cursor.to_a
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
it "returns [] if the delegate returns []" do
|
|
87
|
+
mongo_cursor.stub(:to_a).and_return([])
|
|
88
|
+
cursor.to_a.should == []
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,553 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe "MongoDoc::Document" do
|
|
4
|
+
|
|
5
|
+
describe "#_collection" do
|
|
6
|
+
class DocumentCollectionTest
|
|
7
|
+
include MongoDoc::Document
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
let(:doc) do
|
|
11
|
+
doc = DocumentCollectionTest.new
|
|
12
|
+
doc._root = stub
|
|
13
|
+
doc
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
before do
|
|
17
|
+
DocumentCollectionTest.stub(:collection).and_return('collection')
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
it "delegates to the root's collection" do
|
|
21
|
+
doc._root.should_receive :_collection
|
|
22
|
+
doc._collection
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
context "satisfies form_for requirements" do
|
|
27
|
+
class FormForTest
|
|
28
|
+
include MongoDoc::Document
|
|
29
|
+
|
|
30
|
+
attr_accessor :data
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
before do
|
|
34
|
+
@doc = FormForTest.new
|
|
35
|
+
@doc._id = '1'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
it "#id returns the _id" do
|
|
39
|
+
@doc.id.should == @doc._id
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it "#to_param returns the string of the _id" do
|
|
43
|
+
@doc.to_param.should == @doc._id.to_s
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
context "#new_record?" do
|
|
47
|
+
it "is true when the object does not have an _id" do
|
|
48
|
+
@doc._id = nil
|
|
49
|
+
@doc.should be_new_record
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it "is false when the object has an id" do
|
|
53
|
+
@doc.should_not be_new_record
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
it "#initialize takes a hash" do
|
|
58
|
+
data = 'data'
|
|
59
|
+
FormForTest.new(:data => data).data.should == data
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
context "saving" do
|
|
64
|
+
class SaveRoot
|
|
65
|
+
include MongoDoc::Document
|
|
66
|
+
|
|
67
|
+
embed_many :save_children
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class SaveChild
|
|
71
|
+
include MongoDoc::Document
|
|
72
|
+
|
|
73
|
+
attr_accessor :data
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
before do
|
|
77
|
+
@root = SaveRoot.new
|
|
78
|
+
@root.stub(:_save)
|
|
79
|
+
@child = SaveChild.new
|
|
80
|
+
@root.save_children << @child
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
context "#save" do
|
|
84
|
+
it "delegates to the root" do
|
|
85
|
+
validate = true
|
|
86
|
+
@root.should_receive(:save).with(validate)
|
|
87
|
+
@child.save(validate)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
context "when validating" do
|
|
91
|
+
it "validates" do
|
|
92
|
+
@root.should_receive(:valid?)
|
|
93
|
+
@root.save(true)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
context "and valid" do
|
|
97
|
+
it "delegates to _save" do
|
|
98
|
+
@root.should_receive(:_save).with(false)
|
|
99
|
+
@root.save(true)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
it "returns the result of _save if valid" do
|
|
103
|
+
id = 'id'
|
|
104
|
+
@root.stub(:valid?).and_return(true)
|
|
105
|
+
@root.should_receive(:_save).and_return(id)
|
|
106
|
+
@root.save(true).should == id
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
context "and invalid" do
|
|
111
|
+
it "does not call _save" do
|
|
112
|
+
@root.stub(:valid?).and_return(false)
|
|
113
|
+
@root.should_not_receive(:_save)
|
|
114
|
+
@root.save(true)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
it "returns false" do
|
|
118
|
+
@root.stub(:valid?).and_return(false)
|
|
119
|
+
@root.save(true).should be_false
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
context "when not validating" do
|
|
125
|
+
it "does not validate" do
|
|
126
|
+
@root.should_not_receive(:valid?)
|
|
127
|
+
@root.save(false)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
it "delegates to _save" do
|
|
131
|
+
@root.should_receive(:_save).with(false)
|
|
132
|
+
@root.save(false)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
it "returns the result of _save" do
|
|
136
|
+
id = 'id'
|
|
137
|
+
@root.stub(:_save).and_return(id)
|
|
138
|
+
@root.save(false).should == id
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
context "#save!" do
|
|
144
|
+
it "delegates to the root" do
|
|
145
|
+
@root.should_receive(:save!)
|
|
146
|
+
@child.save!
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
it "validates" do
|
|
150
|
+
@root.should_receive(:valid?).and_return(true)
|
|
151
|
+
@root.save!
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
it "returns the result of _save if valid" do
|
|
155
|
+
id = 'id'
|
|
156
|
+
@root.stub(:valid?).and_return(true)
|
|
157
|
+
@root.should_receive(:_save).with(true).and_return(id)
|
|
158
|
+
@root.save!.should == id
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
it "raises if invalid" do
|
|
162
|
+
@root.stub(:valid?).and_return(false)
|
|
163
|
+
expect do
|
|
164
|
+
@root.save!
|
|
165
|
+
end.should raise_error(MongoDoc::DocumentInvalidError)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
context "#_save" do
|
|
171
|
+
class SaveTest
|
|
172
|
+
include MongoDoc::Document
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
before do
|
|
176
|
+
@collection = stub('collection')
|
|
177
|
+
@doc = SaveTest.new
|
|
178
|
+
@doc.stub(:_collection).and_return(@collection)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
it "delegates to the collection save" do
|
|
182
|
+
safe = true
|
|
183
|
+
@collection.should_receive(:save)
|
|
184
|
+
@doc.send(:_save, safe)
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
it "sets the _id of the document" do
|
|
188
|
+
id = 'id'
|
|
189
|
+
@collection.stub(:save).and_return(id)
|
|
190
|
+
@doc.send(:_save, true)
|
|
191
|
+
@doc._id.should == id
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
it "returns the _id" do
|
|
195
|
+
id = 'id'
|
|
196
|
+
@collection.stub(:save).and_return(id)
|
|
197
|
+
@doc.send(:_save, true).should == id
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
context "creating" do
|
|
202
|
+
class CreateTest
|
|
203
|
+
include MongoDoc::Document
|
|
204
|
+
|
|
205
|
+
attr_accessor :data
|
|
206
|
+
validates_presence_of :data
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
let(:data) { 'data' }
|
|
210
|
+
let(:instance) { CreateTest.new(:data => data) }
|
|
211
|
+
|
|
212
|
+
before do
|
|
213
|
+
instance.stub(:save)
|
|
214
|
+
instance.stub(:save!)
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
context ".create" do
|
|
218
|
+
it "creates a new document with the attributes" do
|
|
219
|
+
CreateTest.should_receive(:new).with(:data => data).and_return(instance)
|
|
220
|
+
CreateTest.create(:data => data)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
context "with the new document" do
|
|
224
|
+
before do
|
|
225
|
+
CreateTest.stub(:new).and_return(instance)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
it "calls save on the instance with validate => true" do
|
|
229
|
+
instance.should_receive(:save).with(true)
|
|
230
|
+
CreateTest.create(:data => data)
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
it "returns the new object" do
|
|
234
|
+
CreateTest.create(:data => data).should == instance
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
context ".create!" do
|
|
240
|
+
it "creates a new document with the attributes" do
|
|
241
|
+
CreateTest.should_receive(:new).with(:data => data).and_return(instance)
|
|
242
|
+
CreateTest.create!(:data => data)
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
context "with the new document" do
|
|
246
|
+
before do
|
|
247
|
+
CreateTest.stub(:new).and_return(instance)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
it "calls save! on the instance" do
|
|
251
|
+
instance.should_receive(:save!).with(no_args)
|
|
252
|
+
CreateTest.create!(:data => data)
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
it "returns the new object" do
|
|
256
|
+
CreateTest.create!(:data => data).should == instance
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
context "updating attributes" do
|
|
263
|
+
class UpdateAttributesChild
|
|
264
|
+
include MongoDoc::Document
|
|
265
|
+
|
|
266
|
+
attr_accessor :child_data
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
class UpdateAttributes
|
|
270
|
+
include MongoDoc::Document
|
|
271
|
+
|
|
272
|
+
attr_accessor :data
|
|
273
|
+
embed :child
|
|
274
|
+
end
|
|
275
|
+
|
|
276
|
+
let(:collection) { stub(:update => nil) }
|
|
277
|
+
|
|
278
|
+
let(:new_doc) { UpdateAttributes.new }
|
|
279
|
+
|
|
280
|
+
let(:existing_doc) do
|
|
281
|
+
doc = UpdateAttributes.new
|
|
282
|
+
doc._id = 'exists'
|
|
283
|
+
doc.stub(:_collection).and_return(collection)
|
|
284
|
+
doc
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
let(:child) do
|
|
288
|
+
child = UpdateAttributesChild.new
|
|
289
|
+
child._id = 'child exists'
|
|
290
|
+
existing_doc.child = child
|
|
291
|
+
child
|
|
292
|
+
end
|
|
293
|
+
|
|
294
|
+
describe "#update_attributes" do
|
|
295
|
+
it "delegates to save if the doc is a new record" do
|
|
296
|
+
new_doc.should_receive(:save)
|
|
297
|
+
new_doc.update_attributes(:data => 'data')
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
context "with an existing doc" do
|
|
301
|
+
|
|
302
|
+
subject { existing_doc.update_attributes(:data => 'data') }
|
|
303
|
+
|
|
304
|
+
it "sets the attributes" do
|
|
305
|
+
subject
|
|
306
|
+
existing_doc.data.should == 'data'
|
|
307
|
+
end
|
|
308
|
+
|
|
309
|
+
it "validates the doc" do
|
|
310
|
+
existing_doc.should_receive(:valid?)
|
|
311
|
+
subject
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
it "returns false if the doc is not valid" do
|
|
315
|
+
existing_doc.stub(:valid?).and_return(false)
|
|
316
|
+
should be_false
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
it "delegates to collection update" do
|
|
320
|
+
collection.should_receive(:update).with({'_id' => existing_doc._id}, {'$set' => {:data => 'data'}}, :safe => false)
|
|
321
|
+
subject
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
context "that is embedded" do
|
|
325
|
+
it "delegates to the root's collection update" do
|
|
326
|
+
collection.should_receive(:update).with({'_id' => existing_doc._id, 'child._id' => child._id}, {'$set' => {'child.child_data' => 'data'}}, :safe => false)
|
|
327
|
+
child.update_attributes(:child_data => 'data')
|
|
328
|
+
end
|
|
329
|
+
end
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
describe "#update_attributes!" do
|
|
334
|
+
it "delegates to save! if the doc is a new record" do
|
|
335
|
+
new_doc.should_receive(:save!)
|
|
336
|
+
new_doc.update_attributes!(:data => 'data')
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
context "with an existing doc" do
|
|
340
|
+
|
|
341
|
+
subject { existing_doc.update_attributes!(:data => 'data') }
|
|
342
|
+
|
|
343
|
+
it "sets the attributes" do
|
|
344
|
+
subject
|
|
345
|
+
existing_doc.data.should == 'data'
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
it "validates the doc" do
|
|
349
|
+
existing_doc.should_receive(:valid?).and_return(true)
|
|
350
|
+
subject
|
|
351
|
+
end
|
|
352
|
+
|
|
353
|
+
it "raises if not valid" do
|
|
354
|
+
existing_doc.stub(:valid?).and_return(false)
|
|
355
|
+
expect do
|
|
356
|
+
subject
|
|
357
|
+
end.should raise_error(MongoDoc::DocumentInvalidError)
|
|
358
|
+
end
|
|
359
|
+
|
|
360
|
+
it "delegates to collection update" do
|
|
361
|
+
collection.should_receive(:update).with({'_id' => existing_doc._id}, {'$set' => {:data => 'data'}}, :safe => true)
|
|
362
|
+
subject
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
context "that is embedded" do
|
|
366
|
+
it "delegates to the root's collection update" do
|
|
367
|
+
collection.should_receive(:update).with({'_id' => existing_doc._id, 'child._id' => child._id}, {'$set' => {'child.child_data' => 'data'}}, :safe => true)
|
|
368
|
+
child.update_attributes!(:child_data => 'data')
|
|
369
|
+
end
|
|
370
|
+
end
|
|
371
|
+
end
|
|
372
|
+
end
|
|
373
|
+
|
|
374
|
+
describe "#hash_with_modifier_path_keys" do
|
|
375
|
+
it "returns a hash with the keys prepended with the modifier path" do
|
|
376
|
+
new_doc.stub(:_modifier_path).and_return('path.to.root')
|
|
377
|
+
new_doc.send(:hash_with_modifier_path_keys, :name => 1).should == {'path.to.root.name' => 1}
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
describe "bson" do
|
|
383
|
+
class BSONTest
|
|
384
|
+
include MongoDoc::Document
|
|
385
|
+
|
|
386
|
+
attr_accessor :other
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
class BSONDerived < BSONTest
|
|
390
|
+
include MongoDoc::Document
|
|
391
|
+
|
|
392
|
+
attr_accessor :derived
|
|
393
|
+
end
|
|
394
|
+
|
|
395
|
+
class OtherObject
|
|
396
|
+
attr_accessor :value
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
before do
|
|
400
|
+
@value = 'value'
|
|
401
|
+
@other = OtherObject.new
|
|
402
|
+
@other.value = @value
|
|
403
|
+
@doc = BSONTest.new(:other => @other)
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
it "encodes the class for the object" do
|
|
407
|
+
@doc.to_bson[MongoDoc::BSON::CLASS_KEY].should == BSONTest.name
|
|
408
|
+
end
|
|
409
|
+
|
|
410
|
+
it "renders a json representation of the object" do
|
|
411
|
+
@doc.to_bson.should be_bson_eql({MongoDoc::BSON::CLASS_KEY => BSONTest.name, "other" => {MongoDoc::BSON::CLASS_KEY => OtherObject.name, "value" => @value}})
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
it "includes the _id of the object" do
|
|
415
|
+
@doc._id = BSON::ObjectID.new
|
|
416
|
+
@doc.to_bson.should be_bson_eql({MongoDoc::BSON::CLASS_KEY => BSONTest.name, "_id" => @doc._id.to_bson, "other" => {MongoDoc::BSON::CLASS_KEY => OtherObject.name, "value" => @value}})
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
it "roundtrips the object" do
|
|
420
|
+
MongoDoc::BSON.decode(@doc.to_bson).should be_kind_of(BSONTest)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
it "ignores the class hash when the :raw_json option is used" do
|
|
424
|
+
MongoDoc::BSON.decode(@doc.to_bson.except(MongoDoc::BSON::CLASS_KEY), :raw_json => true)['other'].should == @other.to_bson
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
it "allows for derived classes" do
|
|
428
|
+
derived = BSONDerived.new(:other => @other, :derived => 'derived')
|
|
429
|
+
MongoDoc::BSON.decode(derived.to_bson).other.should be_kind_of(OtherObject)
|
|
430
|
+
end
|
|
431
|
+
|
|
432
|
+
it "roundtrips embedded ruby objects" do
|
|
433
|
+
MongoDoc::BSON.decode(@doc.to_bson).other.should be_kind_of(OtherObject)
|
|
434
|
+
end
|
|
435
|
+
|
|
436
|
+
context "associations" do
|
|
437
|
+
context "embed" do
|
|
438
|
+
class TestEmbedBsonDoc
|
|
439
|
+
include MongoDoc::Document
|
|
440
|
+
|
|
441
|
+
embed :subdoc
|
|
442
|
+
end
|
|
443
|
+
|
|
444
|
+
class SubEmbedBsonDoc
|
|
445
|
+
include MongoDoc::Document
|
|
446
|
+
|
|
447
|
+
attr_accessor :attr
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
it "#to_bson renders a bson representation of the document" do
|
|
451
|
+
doc = TestEmbedBsonDoc.new
|
|
452
|
+
subdoc = SubEmbedBsonDoc.new(:attr => "value")
|
|
453
|
+
bson = doc.to_bson
|
|
454
|
+
bson["subdoc"] = subdoc.to_bson
|
|
455
|
+
doc.subdoc = subdoc
|
|
456
|
+
doc.to_bson.should == bson
|
|
457
|
+
end
|
|
458
|
+
|
|
459
|
+
it "roundtrips" do
|
|
460
|
+
doc = TestEmbedBsonDoc.new
|
|
461
|
+
subdoc = SubEmbedBsonDoc.new(:attr => "value")
|
|
462
|
+
doc.subdoc = subdoc
|
|
463
|
+
MongoDoc::BSON.decode(doc.to_bson).should == doc
|
|
464
|
+
end
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
context "embed_many" do
|
|
468
|
+
|
|
469
|
+
class SubEmbedManyBsonDoc
|
|
470
|
+
include MongoDoc::Document
|
|
471
|
+
|
|
472
|
+
attr_accessor :attr
|
|
473
|
+
end
|
|
474
|
+
|
|
475
|
+
class TestEmbedManyBsonDoc
|
|
476
|
+
include MongoDoc::Document
|
|
477
|
+
embed_many :subdoc, :class_name => 'SubEmbedManyBsonDoc'
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
it "#to_bson renders a bson representation of the document" do
|
|
481
|
+
doc = TestEmbedManyBsonDoc.new
|
|
482
|
+
subdoc = SubEmbedManyBsonDoc.new(:attr => "value")
|
|
483
|
+
bson = doc.to_bson
|
|
484
|
+
bson["subdoc"] = [subdoc].to_bson
|
|
485
|
+
doc.subdoc = subdoc
|
|
486
|
+
doc.to_bson.should == bson
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
it "roundtrips" do
|
|
490
|
+
doc = TestEmbedManyBsonDoc.new
|
|
491
|
+
subdoc = SubEmbedManyBsonDoc.new(:attr => "value")
|
|
492
|
+
doc.subdoc = subdoc
|
|
493
|
+
MongoDoc::BSON.decode(doc.to_bson).should == doc
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
it "roundtrips the proxy" do
|
|
497
|
+
doc = TestEmbedManyBsonDoc.new(:subdoc => SubEmbedManyBsonDoc.new(:attr => "value"))
|
|
498
|
+
MongoDoc::Associations::CollectionProxy.should === MongoDoc::BSON.decode(doc.to_bson).subdoc
|
|
499
|
+
end
|
|
500
|
+
end
|
|
501
|
+
end
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
context "removing documents" do
|
|
505
|
+
class RemoveDocument
|
|
506
|
+
include MongoDoc::Document
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
let(:doc) { RemoveDocument.new }
|
|
510
|
+
|
|
511
|
+
context "#remove" do
|
|
512
|
+
it "when called on a embedded document with a _root raises UnsupportedOperation" do
|
|
513
|
+
doc._root = RemoveDocument.new
|
|
514
|
+
expect { doc.remove }.to raise_error(MongoDoc::UnsupportedOperation)
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
it "delegates to remove document" do
|
|
518
|
+
doc.should_receive(:remove_document)
|
|
519
|
+
doc.remove
|
|
520
|
+
end
|
|
521
|
+
end
|
|
522
|
+
|
|
523
|
+
context "#remove_document" do
|
|
524
|
+
it "when the document is the root, removes the document" do
|
|
525
|
+
doc.should_receive(:_remove)
|
|
526
|
+
doc.remove_document
|
|
527
|
+
end
|
|
528
|
+
|
|
529
|
+
it "when the document is not the root, calls remove_document on the root" do
|
|
530
|
+
doc._root = root = RemoveDocument.new
|
|
531
|
+
root.should_receive(:remove_document)
|
|
532
|
+
doc.remove_document
|
|
533
|
+
end
|
|
534
|
+
end
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
context "misc class methods" do
|
|
538
|
+
class ClassMethods
|
|
539
|
+
include MongoDoc::Document
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
it ".collection_name returns the name of the collection for this class" do
|
|
543
|
+
ClassMethods.collection_name.should == ClassMethods.to_s.tableize.gsub('/', '.')
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
it ".collection returns a wrapped MongoDoc::Collection" do
|
|
547
|
+
db = stub('db')
|
|
548
|
+
db.should_receive(:collection).with(ClassMethods.to_s.tableize.gsub('/', '.'))
|
|
549
|
+
MongoDoc::Connection.should_receive(:database).and_return(db)
|
|
550
|
+
MongoDoc::Collection.should === ClassMethods.collection
|
|
551
|
+
end
|
|
552
|
+
end
|
|
553
|
+
end
|