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.
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