mongo_doc_rails2 0.6.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (142) hide show
  1. data/.document +5 -0
  2. data/.gitignore +8 -0
  3. data/HISTORY.md +11 -0
  4. data/LICENSE +20 -0
  5. data/README.textile +185 -0
  6. data/Rakefile +188 -0
  7. data/TODO +40 -0
  8. data/VERSION +1 -0
  9. data/data/.gitignore +2 -0
  10. data/examples/simple_document.rb +46 -0
  11. data/examples/simple_object.rb +34 -0
  12. data/features/collections.feature +9 -0
  13. data/features/embed_hash.feature +16 -0
  14. data/features/finders.feature +76 -0
  15. data/features/indexes.feature +28 -0
  16. data/features/mongodb.yml +7 -0
  17. data/features/mongodoc_base.feature +128 -0
  18. data/features/new_record.feature +36 -0
  19. data/features/partial_updates.feature +95 -0
  20. data/features/removing_documents.feature +68 -0
  21. data/features/saving_an_object.feature +15 -0
  22. data/features/scopes.feature +66 -0
  23. data/features/step_definitions/collection_steps.rb +17 -0
  24. data/features/step_definitions/document_steps.rb +149 -0
  25. data/features/step_definitions/documents.rb +40 -0
  26. data/features/step_definitions/embed_hash_steps.rb +6 -0
  27. data/features/step_definitions/finder_steps.rb +15 -0
  28. data/features/step_definitions/index_steps.rb +10 -0
  29. data/features/step_definitions/json_steps.rb +9 -0
  30. data/features/step_definitions/object_steps.rb +50 -0
  31. data/features/step_definitions/objects.rb +24 -0
  32. data/features/step_definitions/partial_update_steps.rb +31 -0
  33. data/features/step_definitions/query_steps.rb +66 -0
  34. data/features/step_definitions/removing_documents_steps.rb +14 -0
  35. data/features/step_definitions/scope_steps.rb +18 -0
  36. data/features/step_definitions/string_casting_steps.rb +29 -0
  37. data/features/step_definitions/util_steps.rb +7 -0
  38. data/features/string_casting.feature +10 -0
  39. data/features/support/support.rb +10 -0
  40. data/features/using_criteria.feature +142 -0
  41. data/lib/mongo_doc.rb +12 -0
  42. data/lib/mongo_doc/associations.rb +109 -0
  43. data/lib/mongo_doc/associations/collection_proxy.rb +121 -0
  44. data/lib/mongo_doc/associations/document_proxy.rb +65 -0
  45. data/lib/mongo_doc/associations/hash_proxy.rb +102 -0
  46. data/lib/mongo_doc/associations/proxy_base.rb +48 -0
  47. data/lib/mongo_doc/attributes.rb +84 -0
  48. data/lib/mongo_doc/bson.rb +31 -0
  49. data/lib/mongo_doc/collection.rb +82 -0
  50. data/lib/mongo_doc/connection.rb +88 -0
  51. data/lib/mongo_doc/contexts.rb +31 -0
  52. data/lib/mongo_doc/contexts/ids.rb +41 -0
  53. data/lib/mongo_doc/contexts/mongo.rb +272 -0
  54. data/lib/mongo_doc/criteria.rb +70 -0
  55. data/lib/mongo_doc/cursor.rb +32 -0
  56. data/lib/mongo_doc/document.rb +205 -0
  57. data/lib/mongo_doc/ext.rb +16 -0
  58. data/lib/mongo_doc/ext/array.rb +5 -0
  59. data/lib/mongo_doc/ext/binary.rb +7 -0
  60. data/lib/mongo_doc/ext/boolean_class.rb +17 -0
  61. data/lib/mongo_doc/ext/date.rb +19 -0
  62. data/lib/mongo_doc/ext/date_time.rb +17 -0
  63. data/lib/mongo_doc/ext/dbref.rb +7 -0
  64. data/lib/mongo_doc/ext/hash.rb +7 -0
  65. data/lib/mongo_doc/ext/min_max_keys.rb +13 -0
  66. data/lib/mongo_doc/ext/nil_class.rb +5 -0
  67. data/lib/mongo_doc/ext/numeric.rb +17 -0
  68. data/lib/mongo_doc/ext/object.rb +19 -0
  69. data/lib/mongo_doc/ext/object_id.rb +7 -0
  70. data/lib/mongo_doc/ext/regexp.rb +5 -0
  71. data/lib/mongo_doc/ext/string.rb +5 -0
  72. data/lib/mongo_doc/ext/symbol.rb +5 -0
  73. data/lib/mongo_doc/ext/time.rb +9 -0
  74. data/lib/mongo_doc/finders.rb +38 -0
  75. data/lib/mongo_doc/index.rb +46 -0
  76. data/lib/mongo_doc/matchers.rb +35 -0
  77. data/lib/mongo_doc/root.rb +26 -0
  78. data/lib/mongo_doc/scope.rb +64 -0
  79. data/lib/mongo_doc/validations.rb +12 -0
  80. data/lib/mongo_doc/validations/macros.rb +11 -0
  81. data/lib/mongo_doc/validations/validates_embedded.rb +13 -0
  82. data/lib/mongoid/contexts/enumerable.rb +151 -0
  83. data/lib/mongoid/contexts/paging.rb +42 -0
  84. data/lib/mongoid/criteria.rb +239 -0
  85. data/lib/mongoid/criterion/complex.rb +21 -0
  86. data/lib/mongoid/criterion/exclusion.rb +65 -0
  87. data/lib/mongoid/criterion/inclusion.rb +93 -0
  88. data/lib/mongoid/criterion/optional.rb +136 -0
  89. data/lib/mongoid/extensions/hash/criteria_helpers.rb +20 -0
  90. data/lib/mongoid/extensions/symbol/inflections.rb +36 -0
  91. data/lib/mongoid/matchers/all.rb +11 -0
  92. data/lib/mongoid/matchers/default.rb +26 -0
  93. data/lib/mongoid/matchers/exists.rb +13 -0
  94. data/lib/mongoid/matchers/gt.rb +11 -0
  95. data/lib/mongoid/matchers/gte.rb +11 -0
  96. data/lib/mongoid/matchers/in.rb +11 -0
  97. data/lib/mongoid/matchers/lt.rb +11 -0
  98. data/lib/mongoid/matchers/lte.rb +11 -0
  99. data/lib/mongoid/matchers/ne.rb +11 -0
  100. data/lib/mongoid/matchers/nin.rb +11 -0
  101. data/lib/mongoid/matchers/size.rb +11 -0
  102. data/mongo_doc_rails2.gemspec +237 -0
  103. data/mongod.example.yml +2 -0
  104. data/mongodb.example.yml +14 -0
  105. data/perf/mongo_doc_object.rb +83 -0
  106. data/perf/mongo_document.rb +84 -0
  107. data/perf/ruby_driver.rb +49 -0
  108. data/script/console +8 -0
  109. data/spec/array_including_argument_matcher.rb +62 -0
  110. data/spec/associations/collection_proxy_spec.rb +233 -0
  111. data/spec/associations/document_proxy_spec.rb +45 -0
  112. data/spec/associations/hash_proxy_spec.rb +181 -0
  113. data/spec/associations/proxy_base_spec.rb +92 -0
  114. data/spec/associations_spec.rb +218 -0
  115. data/spec/attributes_accessor_spec.rb +33 -0
  116. data/spec/attributes_spec.rb +145 -0
  117. data/spec/bson_matchers.rb +54 -0
  118. data/spec/bson_spec.rb +196 -0
  119. data/spec/collection_spec.rb +169 -0
  120. data/spec/connection_spec.rb +147 -0
  121. data/spec/contexts/ids_spec.rb +49 -0
  122. data/spec/contexts/mongo_spec.rb +235 -0
  123. data/spec/contexts_spec.rb +56 -0
  124. data/spec/criteria_spec.rb +69 -0
  125. data/spec/cursor_spec.rb +91 -0
  126. data/spec/document_ext.rb +9 -0
  127. data/spec/document_spec.rb +553 -0
  128. data/spec/embedded_save_spec.rb +73 -0
  129. data/spec/ext_spec.rb +89 -0
  130. data/spec/finders_spec.rb +61 -0
  131. data/spec/hash_matchers.rb +27 -0
  132. data/spec/index_spec.rb +79 -0
  133. data/spec/matchers_spec.rb +342 -0
  134. data/spec/mongodb.yml +6 -0
  135. data/spec/mongodb_pairs.yml +8 -0
  136. data/spec/new_record_spec.rb +128 -0
  137. data/spec/root_spec.rb +41 -0
  138. data/spec/scope_spec.rb +79 -0
  139. data/spec/spec.opts +2 -0
  140. data/spec/spec_helper.rb +14 -0
  141. data/spec/validations_spec.rb +30 -0
  142. 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
@@ -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,9 @@
1
+ module MongoDoc
2
+ module Document
3
+ def errors_on(attribute)
4
+ self.valid?
5
+ [self.errors.on(attribute)].flatten.compact
6
+ end
7
+ alias :error_on :errors_on
8
+ end
9
+ 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