jmonteiro-mongo_mapper 0.1.0

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 (91) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +38 -0
  4. data/Rakefile +55 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +60 -0
  7. data/jmonteiro-mongo_mapper.gemspec +195 -0
  8. data/lib/mongo_mapper.rb +128 -0
  9. data/lib/mongo_mapper/descendant_appends.rb +44 -0
  10. data/lib/mongo_mapper/document.rb +402 -0
  11. data/lib/mongo_mapper/dynamic_finder.rb +74 -0
  12. data/lib/mongo_mapper/embedded_document.rb +61 -0
  13. data/lib/mongo_mapper/finder_options.rb +127 -0
  14. data/lib/mongo_mapper/plugins.rb +19 -0
  15. data/lib/mongo_mapper/plugins/associations.rb +104 -0
  16. data/lib/mongo_mapper/plugins/associations/base.rb +121 -0
  17. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +28 -0
  18. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +23 -0
  19. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  20. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +49 -0
  21. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +139 -0
  22. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  23. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +117 -0
  24. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  25. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  26. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  27. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +66 -0
  28. data/lib/mongo_mapper/plugins/associations/proxy.rb +118 -0
  29. data/lib/mongo_mapper/plugins/callbacks.rb +65 -0
  30. data/lib/mongo_mapper/plugins/clone.rb +13 -0
  31. data/lib/mongo_mapper/plugins/descendants.rb +16 -0
  32. data/lib/mongo_mapper/plugins/dirty.rb +119 -0
  33. data/lib/mongo_mapper/plugins/equality.rb +11 -0
  34. data/lib/mongo_mapper/plugins/identity_map.rb +66 -0
  35. data/lib/mongo_mapper/plugins/inspect.rb +14 -0
  36. data/lib/mongo_mapper/plugins/keys.rb +295 -0
  37. data/lib/mongo_mapper/plugins/logger.rb +17 -0
  38. data/lib/mongo_mapper/plugins/pagination.rb +85 -0
  39. data/lib/mongo_mapper/plugins/protected.rb +31 -0
  40. data/lib/mongo_mapper/plugins/rails.rb +80 -0
  41. data/lib/mongo_mapper/plugins/serialization.rb +109 -0
  42. data/lib/mongo_mapper/plugins/validations.rb +48 -0
  43. data/lib/mongo_mapper/support.rb +213 -0
  44. data/performance/read_write.rb +52 -0
  45. data/specs.watchr +51 -0
  46. data/test/NOTE_ON_TESTING +1 -0
  47. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +63 -0
  48. data/test/functional/associations/test_belongs_to_proxy.rb +93 -0
  49. data/test/functional/associations/test_in_array_proxy.rb +309 -0
  50. data/test/functional/associations/test_many_documents_as_proxy.rb +246 -0
  51. data/test/functional/associations/test_many_documents_proxy.rb +437 -0
  52. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +175 -0
  53. data/test/functional/associations/test_many_embedded_proxy.rb +216 -0
  54. data/test/functional/associations/test_many_polymorphic_proxy.rb +340 -0
  55. data/test/functional/associations/test_one_proxy.rb +149 -0
  56. data/test/functional/test_associations.rb +44 -0
  57. data/test/functional/test_binary.rb +27 -0
  58. data/test/functional/test_callbacks.rb +81 -0
  59. data/test/functional/test_dirty.rb +156 -0
  60. data/test/functional/test_document.rb +1171 -0
  61. data/test/functional/test_embedded_document.rb +125 -0
  62. data/test/functional/test_identity_map.rb +233 -0
  63. data/test/functional/test_logger.rb +20 -0
  64. data/test/functional/test_modifiers.rb +252 -0
  65. data/test/functional/test_pagination.rb +93 -0
  66. data/test/functional/test_protected.rb +41 -0
  67. data/test/functional/test_string_id_compatibility.rb +67 -0
  68. data/test/functional/test_validations.rb +329 -0
  69. data/test/models.rb +232 -0
  70. data/test/support/custom_matchers.rb +55 -0
  71. data/test/support/timing.rb +16 -0
  72. data/test/test_helper.rb +60 -0
  73. data/test/unit/associations/test_base.rb +207 -0
  74. data/test/unit/associations/test_proxy.rb +103 -0
  75. data/test/unit/serializers/test_json_serializer.rb +189 -0
  76. data/test/unit/test_descendant_appends.rb +71 -0
  77. data/test/unit/test_document.rb +203 -0
  78. data/test/unit/test_dynamic_finder.rb +125 -0
  79. data/test/unit/test_embedded_document.rb +628 -0
  80. data/test/unit/test_finder_options.rb +325 -0
  81. data/test/unit/test_keys.rb +169 -0
  82. data/test/unit/test_mongo_mapper.rb +65 -0
  83. data/test/unit/test_pagination.rb +127 -0
  84. data/test/unit/test_plugins.rb +42 -0
  85. data/test/unit/test_rails.rb +139 -0
  86. data/test/unit/test_rails_compatibility.rb +42 -0
  87. data/test/unit/test_serialization.rb +51 -0
  88. data/test/unit/test_support.rb +350 -0
  89. data/test/unit/test_time_zones.rb +39 -0
  90. data/test/unit/test_validations.rb +492 -0
  91. metadata +260 -0
@@ -0,0 +1,125 @@
1
+ require 'test_helper'
2
+
3
+ class DynamicFinderTest < Test::Unit::TestCase
4
+ include MongoMapper
5
+
6
+ should "initialize with method" do
7
+ finder = DynamicFinder.new(:foobar)
8
+ finder.method.should == :foobar
9
+ end
10
+
11
+ context "found?" do
12
+ should "be true for find_by" do
13
+ DynamicFinder.new(:find_by_foo).found?.should be_true
14
+ end
15
+
16
+ should "be true for find_by with !" do
17
+ DynamicFinder.new(:find_by_foo!).found?.should be_true
18
+ end
19
+
20
+ should "be true for find_all_by" do
21
+ DynamicFinder.new(:find_all_by_foo).found?.should be_true
22
+ end
23
+
24
+ should "be true for find_or_initialize_by" do
25
+ DynamicFinder.new(:find_or_initialize_by_foo).found?.should be_true
26
+ end
27
+
28
+ should "be true for find_or_create_by" do
29
+ DynamicFinder.new(:find_or_create_by_foo).found?.should be_true
30
+ end
31
+
32
+ should "be false for anything else" do
33
+ [:foobar, :bazwick].each do |method|
34
+ DynamicFinder.new(method).found?.should be_false
35
+ end
36
+ end
37
+ end
38
+
39
+ context "find_all_by" do
40
+ should "parse one attribute" do
41
+ DynamicFinder.new(:find_all_by_foo).attributes.should == %w(foo)
42
+ end
43
+
44
+ should "parse multiple attributes" do
45
+ DynamicFinder.new(:find_all_by_foo_and_bar).attributes.should == %w(foo bar)
46
+ DynamicFinder.new(:find_all_by_foo_and_bar_and_baz).attributes.should == %w(foo bar baz)
47
+ end
48
+
49
+ should "set finder to :all" do
50
+ DynamicFinder.new(:find_all_by_foo_and_bar).finder.should == :all
51
+ end
52
+ end
53
+
54
+ context "find_by" do
55
+ should "parse one attribute" do
56
+ DynamicFinder.new(:find_by_foo).attributes.should == %w(foo)
57
+ end
58
+
59
+ should "parse multiple attributes" do
60
+ DynamicFinder.new(:find_by_foo_and_bar).attributes.should == %w(foo bar)
61
+ end
62
+
63
+ should "set finder to :first" do
64
+ DynamicFinder.new(:find_by_foo).finder.should == :first
65
+ end
66
+
67
+ should "set bang to false" do
68
+ DynamicFinder.new(:find_by_foo).bang.should be_false
69
+ end
70
+ end
71
+
72
+ context "find_by with !" do
73
+ should "parse one attribute" do
74
+ DynamicFinder.new(:find_by_foo!).attributes.should == %w(foo)
75
+ end
76
+
77
+ should "parse multiple attributes" do
78
+ DynamicFinder.new(:find_by_foo_and_bar!).attributes.should == %w(foo bar)
79
+ end
80
+
81
+ should "set finder to :first" do
82
+ DynamicFinder.new(:find_by_foo!).finder.should == :first
83
+ end
84
+
85
+ should "set bang to true" do
86
+ DynamicFinder.new(:find_by_foo!).bang.should be_true
87
+ end
88
+ end
89
+
90
+ context "find_or_initialize_by" do
91
+ should "parse one attribute" do
92
+ DynamicFinder.new(:find_or_initialize_by_foo).attributes.should == %w(foo)
93
+ end
94
+
95
+ should "parse multiple attributes" do
96
+ DynamicFinder.new(:find_or_initialize_by_foo_and_bar).attributes.should == %w(foo bar)
97
+ end
98
+
99
+ should "set finder to :first" do
100
+ DynamicFinder.new(:find_or_initialize_by_foo).finder.should == :first
101
+ end
102
+
103
+ should "set instantiator to new" do
104
+ DynamicFinder.new(:find_or_initialize_by_foo).instantiator.should == :new
105
+ end
106
+ end
107
+
108
+ context "find_or_create_by" do
109
+ should "parse one attribute" do
110
+ DynamicFinder.new(:find_or_create_by_foo).attributes.should == %w(foo)
111
+ end
112
+
113
+ should "parse multiple attributes" do
114
+ DynamicFinder.new(:find_or_create_by_foo_and_bar).attributes.should == %w(foo bar)
115
+ end
116
+
117
+ should "set finder to :first" do
118
+ DynamicFinder.new(:find_or_create_by_foo).finder.should == :first
119
+ end
120
+
121
+ should "set instantiator to new" do
122
+ DynamicFinder.new(:find_or_create_by_foo).instantiator.should == :create
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,628 @@
1
+ require 'test_helper'
2
+
3
+ class Grandparent
4
+ include MongoMapper::EmbeddedDocument
5
+ key :grandparent, String
6
+ end
7
+
8
+ class Parent < Grandparent
9
+ include MongoMapper::EmbeddedDocument
10
+ key :parent, String
11
+ end
12
+
13
+ class Child < Parent
14
+ include MongoMapper::EmbeddedDocument
15
+ key :child, String
16
+ end
17
+
18
+ module KeyOverride
19
+ def other_child
20
+ self[:other_child] || "special result"
21
+ end
22
+
23
+ def other_child=(value)
24
+ super(value + " modified")
25
+ end
26
+ end
27
+
28
+ class OtherChild < Parent
29
+ include MongoMapper::EmbeddedDocument
30
+ include KeyOverride
31
+
32
+ key :other_child, String
33
+ end
34
+
35
+ class EmbeddedDocumentTest < Test::Unit::TestCase
36
+ context "Including MongoMapper::EmbeddedDocument in a class" do
37
+ setup do
38
+ @klass = EDoc()
39
+ end
40
+
41
+ should "add _id key" do
42
+ @klass.keys['_id'].should_not be_nil
43
+ end
44
+
45
+ should "know it is using object id" do
46
+ @klass.using_object_id?.should be_true
47
+ end
48
+
49
+ should "know it is not using object id if _id type is changed" do
50
+ @klass.key :_id, String
51
+ @klass.using_object_id?.should be_false
52
+ end
53
+ end
54
+
55
+ context "Class Methods" do
56
+ should "include logger" do
57
+ @klass = EDoc()
58
+ @klass.logger.should == MongoMapper.logger
59
+ @klass.logger.should be_instance_of(Logger)
60
+ end
61
+
62
+ should "return false for embeddable" do
63
+ EDoc().embeddable?.should be_true
64
+ end
65
+
66
+ context "#to_mongo" do
67
+ setup { @klass = EDoc() }
68
+
69
+ should "be nil if nil" do
70
+ @klass.to_mongo(nil).should be_nil
71
+ end
72
+
73
+ should "convert to_mongo for other values" do
74
+ doc = @klass.new(:foo => 'bar')
75
+ to_mongo = @klass.to_mongo(doc)
76
+ to_mongo.is_a?(Hash).should be_true
77
+ to_mongo['foo'].should == 'bar'
78
+ end
79
+ end
80
+
81
+ context "#from_mongo" do
82
+ setup { @klass = EDoc() }
83
+
84
+ should "be nil if nil" do
85
+ @klass.from_mongo(nil).should be_nil
86
+ end
87
+
88
+ should "be instance if instance of class" do
89
+ doc = @klass.new
90
+ @klass.from_mongo(doc).should == doc
91
+ end
92
+
93
+ should "be instance if hash of attributes" do
94
+ doc = @klass.from_mongo({:foo => 'bar'})
95
+ doc.instance_of?(@klass).should be_true
96
+ doc.foo.should == 'bar'
97
+ end
98
+ end
99
+
100
+ context "defining a key" do
101
+ setup do
102
+ @document = EDoc()
103
+ end
104
+
105
+ should "work with name" do
106
+ key = @document.key(:name)
107
+ key.name.should == 'name'
108
+ end
109
+
110
+ should "work with name and type" do
111
+ key = @document.key(:name, String)
112
+ key.name.should == 'name'
113
+ key.type.should == String
114
+ end
115
+
116
+ should "work with name, type and options" do
117
+ key = @document.key(:name, String, :required => true)
118
+ key.name.should == 'name'
119
+ key.type.should == String
120
+ key.options[:required].should be_true
121
+ end
122
+
123
+ should "work with name and options" do
124
+ key = @document.key(:name, :required => true)
125
+ key.name.should == 'name'
126
+ key.options[:required].should be_true
127
+ end
128
+
129
+ should "be tracked per document" do
130
+ @document.key(:name, String)
131
+ @document.key(:age, Integer)
132
+ @document.keys['name'].name.should == 'name'
133
+ @document.keys['name'].type.should == String
134
+ @document.keys['age'].name.should == 'age'
135
+ @document.keys['age'].type.should == Integer
136
+ end
137
+
138
+ should "be redefinable" do
139
+ @document.key(:foo, String)
140
+ @document.keys['foo'].type.should == String
141
+ @document.key(:foo, Integer)
142
+ @document.keys['foo'].type.should == Integer
143
+ end
144
+
145
+ should "create reader method" do
146
+ @document.new.should_not respond_to(:foo)
147
+ @document.key(:foo, String)
148
+ @document.new.should respond_to(:foo)
149
+ end
150
+
151
+ should "create reader before typecast method" do
152
+ @document.new.should_not respond_to(:foo_before_typecast)
153
+ @document.key(:foo, String)
154
+ @document.new.should respond_to(:foo_before_typecast)
155
+ end
156
+
157
+ should "create writer method" do
158
+ @document.new.should_not respond_to(:foo=)
159
+ @document.key(:foo, String)
160
+ @document.new.should respond_to(:foo=)
161
+ end
162
+
163
+ should "create boolean method" do
164
+ @document.new.should_not respond_to(:foo?)
165
+ @document.key(:foo, String)
166
+ @document.new.should respond_to(:foo?)
167
+ end
168
+ end
169
+
170
+ context "keys" do
171
+ should "be inherited" do
172
+ Grandparent.keys.keys.sort.should == ['_id', 'grandparent']
173
+ Parent.keys.keys.sort.should == ['_id', 'grandparent', 'parent']
174
+ Child.keys.keys.sort.should == ['_id', 'child', 'grandparent', 'parent']
175
+ end
176
+
177
+ should "propogate to descendants if key added after class definition" do
178
+ Grandparent.key :_type, String
179
+
180
+ Grandparent.keys.keys.sort.should == ['_id', '_type', 'grandparent']
181
+ Parent.keys.keys.sort.should == ['_id', '_type', 'grandparent', 'parent']
182
+ Child.keys.keys.sort.should == ['_id', '_type', 'child', 'grandparent', 'parent']
183
+ end
184
+
185
+ should "not add anonymous objects to the ancestor tree" do
186
+ OtherChild.ancestors.any? { |a| a.name.blank? }.should be_false
187
+ end
188
+
189
+ should "not include descendant keys" do
190
+ lambda { Parent.new.other_child }.should raise_error
191
+ end
192
+ end
193
+
194
+ context "descendants" do
195
+ should "default to nil" do
196
+ Child.descendants.should be_nil
197
+ end
198
+
199
+ should "be recorded" do
200
+ Grandparent.descendants.should == [Parent]
201
+ Parent.descendants.should == [Child, OtherChild]
202
+ end
203
+ end
204
+ end
205
+
206
+ context "An instance of an embedded document" do
207
+ setup do
208
+ @document = EDoc do
209
+ key :name, String
210
+ key :age, Integer
211
+ end
212
+ end
213
+
214
+ should "have to_param that is string representation of id" do
215
+ doc = @document.new
216
+ doc.to_param.should == doc.id.to_s
217
+ doc.to_param.should be_instance_of(String)
218
+ end
219
+
220
+ should "have access to class logger" do
221
+ doc = @document.new
222
+ doc.logger.should == @document.logger
223
+ doc.logger.should be_instance_of(Logger)
224
+ end
225
+
226
+ should "automatically have an _id key" do
227
+ @document.keys.keys.should include('_id')
228
+ end
229
+
230
+ should "create id during initialization" do
231
+ @document.new._id.should be_instance_of(Mongo::ObjectID)
232
+ end
233
+
234
+ should "have id method returns _id" do
235
+ id = Mongo::ObjectID.new
236
+ doc = @document.new(:_id => id)
237
+ doc.id.should == id
238
+ end
239
+
240
+ should "convert string object id to mongo object id when assigning id with _id object id type" do
241
+ id = Mongo::ObjectID.new
242
+
243
+ doc = @document.new(:id => id.to_s)
244
+ doc._id.should == id
245
+ doc.id.should == id
246
+
247
+ doc = @document.new(:_id => id.to_s)
248
+ doc._id.should == id
249
+ doc.id.should == id
250
+ end
251
+
252
+ context "_root_document" do
253
+ should "default to nil" do
254
+ @document.new._root_document.should be_nil
255
+ end
256
+
257
+ should "allow setting when initialized" do
258
+ root = Doc().new
259
+ doc = @document.new :_root_document => root
260
+
261
+ doc._root_document.should be(root)
262
+ end
263
+
264
+ should "also be set on many embedded documents" do
265
+ root = Doc().new
266
+ klass = EDoc { many :children }
267
+ doc = klass.new(:_root_document => root, :children => [{}])
268
+
269
+ doc.children.first._root_document.should == root
270
+ end
271
+ end
272
+
273
+ context "being initialized" do
274
+ should "accept a hash that sets keys and values" do
275
+ doc = @document.new(:name => 'John', :age => 23)
276
+ doc.attributes.keys.sort.should == ['_id', 'age', 'name']
277
+ doc.attributes['name'].should == 'John'
278
+ doc.attributes['age'].should == 23
279
+ end
280
+
281
+ should "be able to assign keys dynamically" do
282
+ doc = @document.new(:name => 'John', :skills => ['ruby', 'rails'])
283
+ doc.name.should == 'John'
284
+ doc.skills.should == ['ruby', 'rails']
285
+ end
286
+
287
+ should "not throw error if initialized with nil" do
288
+ assert_nothing_raised { @document.new(nil) }
289
+ end
290
+ end
291
+
292
+ context "initialized when _type key present" do
293
+ setup do
294
+ @klass = EDoc('FooBar') { key :_type, String }
295
+ end
296
+
297
+ should "set _type to class name" do
298
+ @klass.new._type.should == 'FooBar'
299
+ end
300
+
301
+ should "not change _type if already set" do
302
+ @klass.new(:_type => 'Foo')._type.should == 'Foo'
303
+ end
304
+ end
305
+
306
+ context "attributes=" do
307
+ should "update values for keys provided" do
308
+ doc = @document.new(:name => 'foobar', :age => 10)
309
+ doc.attributes = {:name => 'new value', :age => 5}
310
+ doc.attributes[:name].should == 'new value'
311
+ doc.attributes[:age].should == 5
312
+ end
313
+
314
+ should "not update values for keys that were not provided" do
315
+ doc = @document.new(:name => 'foobar', :age => 10)
316
+ doc.attributes = {:name => 'new value'}
317
+ doc.attributes[:name].should == 'new value'
318
+ doc.attributes[:age].should == 10
319
+ end
320
+
321
+ should "work with pre-defined methods" do
322
+ @document.class_eval do
323
+ attr_writer :password
324
+
325
+ def passwd
326
+ @password
327
+ end
328
+ end
329
+
330
+ doc = @document.new(:name => 'foobar', :password => 'secret')
331
+ doc.passwd.should == 'secret'
332
+ end
333
+
334
+ should "typecast key values" do
335
+ doc = @document.new(:name => 1234, :age => '21')
336
+ doc.name.should == '1234'
337
+ doc.age.should == 21
338
+ end
339
+ end
340
+
341
+ context "attributes" do
342
+ should "default to hash with all keys" do
343
+ doc = @document.new
344
+ doc.attributes.keys.sort.should == ['_id', 'age', 'name']
345
+ end
346
+
347
+ should "return all keys with values" do
348
+ doc = @document.new(:name => 'string', :age => nil)
349
+ doc.attributes.keys.sort.should == ['_id', 'age', 'name']
350
+ doc.attributes.values.should include('string')
351
+ doc.attributes.values.should include(nil)
352
+ end
353
+
354
+ should "have indifferent access" do
355
+ doc = @document.new(:name => 'string')
356
+ doc.attributes[:name].should == 'string'
357
+ doc.attributes['name'].should == 'string'
358
+ end
359
+ end
360
+
361
+ context "to_mongo" do
362
+ should "default to hash with _id key" do
363
+ doc = @document.new
364
+ doc.to_mongo.keys.sort.should == ['_id', 'age', 'name']
365
+ end
366
+
367
+ should "return all keys" do
368
+ doc = @document.new(:name => 'string', :age => nil)
369
+ doc.to_mongo.keys.sort.should == ['_id', 'age', 'name']
370
+ doc.to_mongo.values.should include('string')
371
+ doc.to_mongo.values.should include(nil)
372
+ end
373
+ end
374
+
375
+ context "clone" do
376
+ should "regenerate the id" do
377
+ doc = @document.new(:name => "foo", :age => 27)
378
+ doc_id = doc.id
379
+ clone = doc.clone
380
+ clone_id = clone.id
381
+ clone_id.should_not == doc_id
382
+ end
383
+
384
+ should "copy the attributes" do
385
+ doc = @document.new(:name => "foo", :age => 27)
386
+ clone = doc.clone
387
+ clone.name.should == "foo"
388
+ clone.age.should == 27
389
+ end
390
+ end
391
+
392
+ context "key shorcut access" do
393
+ context "[]" do
394
+ should "work when key found" do
395
+ doc = @document.new(:name => 'string')
396
+ doc[:name].should == 'string'
397
+ end
398
+
399
+ should "raise exception when key not found" do
400
+ doc = @document.new(:name => 'string')
401
+ assert_raises(MongoMapper::KeyNotFound) { doc[:not_here] }
402
+ end
403
+ end
404
+
405
+ context "[]=" do
406
+ should "write key value for existing key" do
407
+ doc = @document.new
408
+ doc[:name] = 'string'
409
+ doc[:name].should == 'string'
410
+ end
411
+
412
+ should "create key and write value for missing key" do
413
+ doc = @document.new
414
+ doc[:foo] = 'string'
415
+ doc.class.keys.include?('foo').should be_true
416
+ doc[:foo].should == 'string'
417
+ end
418
+
419
+ should "share the new key with the class" do
420
+ doc = @document.new
421
+ doc[:foo] = 'string'
422
+ @document.keys.should include('foo')
423
+ end
424
+ end
425
+ end
426
+
427
+ context "reading a key" do
428
+ should "work for defined keys" do
429
+ doc = @document.new(:name => 'string')
430
+ doc.name.should == 'string'
431
+ end
432
+
433
+ should "raise no method error for undefined keys" do
434
+ doc = @document.new
435
+ lambda { doc.fart }.should raise_error(NoMethodError)
436
+ end
437
+
438
+ should "be accessible for use in the model" do
439
+ @document.class_eval do
440
+ def name_and_age
441
+ "#{self[:name]} (#{self[:age]})"
442
+ end
443
+ end
444
+
445
+ doc = @document.new(:name => 'John', :age => 27)
446
+ doc.name_and_age.should == 'John (27)'
447
+ end
448
+
449
+ should "set instance variable" do
450
+ @document.key :foo, Array
451
+ doc = @document.new
452
+ doc.instance_variable_get("@foo").should be_nil
453
+ doc.foo
454
+ doc.instance_variable_get("@foo").should == []
455
+ end
456
+
457
+ should "be overrideable by modules" do
458
+ @document = Doc do
459
+ key :other_child, String
460
+ end
461
+
462
+ child = @document.new
463
+ child.other_child.should be_nil
464
+
465
+ @document.send :include, KeyOverride
466
+
467
+ overriden_child = @document.new
468
+ overriden_child.other_child.should == 'special result'
469
+ end
470
+ end
471
+
472
+ context "reading a key before typcasting" do
473
+ should "work for defined keys" do
474
+ doc = @document.new(:name => 12)
475
+ doc.name_before_typecast.should == 12
476
+ end
477
+
478
+ should "raise no method error for undefined keys" do
479
+ doc = @document.new
480
+ lambda { doc.foo_before_typecast }.should raise_error(NoMethodError)
481
+ end
482
+
483
+ should "be accessible for use in a document" do
484
+ @document.class_eval do
485
+ def untypcasted_name
486
+ read_key_before_typecast(:name)
487
+ end
488
+ end
489
+
490
+ doc = @document.new(:name => 12)
491
+ doc.name.should == '12'
492
+ doc.untypcasted_name.should == 12
493
+ end
494
+ end
495
+
496
+ context "writing a key" do
497
+ should "work for defined keys" do
498
+ doc = @document.new
499
+ doc.name = 'John'
500
+ doc.name.should == 'John'
501
+ end
502
+
503
+ should "raise no method error for undefined keys" do
504
+ doc = @document.new
505
+ lambda { doc.fart = 'poof!' }.should raise_error(NoMethodError)
506
+ end
507
+
508
+ should "typecast value" do
509
+ doc = @document.new
510
+ doc.name = 1234
511
+ doc.name.should == '1234'
512
+ doc.age = '21'
513
+ doc.age.should == 21
514
+ end
515
+
516
+ should "be accessible for use in the model" do
517
+ @document.class_eval do
518
+ def name_and_age=(new_value)
519
+ new_value.match(/([^\(\s]+) \((.*)\)/)
520
+ write_key :name, $1
521
+ write_key :age, $2
522
+ end
523
+ end
524
+
525
+ doc = @document.new
526
+ doc.name_and_age = 'Frank (62)'
527
+ doc.name.should == 'Frank'
528
+ doc.age.should == 62
529
+ end
530
+
531
+ should "be overrideable by modules" do
532
+ @document = Doc do
533
+ key :other_child, String
534
+ end
535
+
536
+ child = @document.new(:other_child => 'foo')
537
+ child.other_child.should == 'foo'
538
+
539
+ @document.send :include, KeyOverride
540
+
541
+ overriden_child = @document.new(:other_child => 'foo')
542
+ overriden_child.other_child.should == 'foo modified'
543
+ end
544
+ end # writing a key
545
+
546
+ context "checking if a keys value is present" do
547
+ should "work for defined keys" do
548
+ doc = @document.new
549
+ doc.name?.should be_false
550
+ doc.name = 'John'
551
+ doc.name?.should be_true
552
+ end
553
+
554
+ should "raise no method error for undefined keys" do
555
+ doc = @document.new
556
+ lambda { doc.fart? }.should raise_error(NoMethodError)
557
+ end
558
+ end
559
+
560
+ should "call inspect on the document's attributes instead of to_s when inspecting the document" do
561
+ doc = @document.new(:animals => %w(dog cat))
562
+ doc.inspect.should include(%(animals: ["dog", "cat"]))
563
+ end
564
+
565
+ context "equality" do
566
+ setup do
567
+ @oid = Mongo::ObjectID.new
568
+ end
569
+ should "be equal if id and class are the same" do
570
+ (@document.new('_id' => @oid) == @document.new('_id' => @oid)).should be_true
571
+ end
572
+
573
+ should "not be equal if class same but id different" do
574
+ (@document.new('_id' => @oid) == @document.new('_id' => Mongo::ObjectID.new)).should be_false
575
+ end
576
+
577
+ should "not be equal if id same but class different" do
578
+ another_document = Doc()
579
+ (@document.new('_id' => @oid) == another_document.new('_id' => @oid)).should be_false
580
+ end
581
+ end
582
+
583
+ context "reading keys with default values" do
584
+ setup do
585
+ @document = EDoc do
586
+ key :name, String, :default => 'foo'
587
+ key :age, Integer, :default => 20
588
+ key :net_worth, Float, :default => 100.00
589
+ key :active, Boolean, :default => true
590
+ key :smart, Boolean, :default => false
591
+ key :skills, Array, :default => [1]
592
+ key :options, Hash, :default => {'foo' => 'bar'}
593
+ end
594
+
595
+ @doc = @document.new
596
+ end
597
+
598
+ should "work for strings" do
599
+ @doc.name.should == 'foo'
600
+ end
601
+
602
+ should "work for integers" do
603
+ @doc.age.should == 20
604
+ end
605
+
606
+ should "work for floats" do
607
+ @doc.net_worth.should == 100.00
608
+ end
609
+
610
+ should "work for booleans" do
611
+ @doc.active.should == true
612
+ @doc.smart.should == false
613
+ end
614
+
615
+ should "work for arrays" do
616
+ @doc.skills.should == [1]
617
+ @doc.skills << 2
618
+ @doc.skills.should == [1, 2]
619
+ end
620
+
621
+ should "work for hashes" do
622
+ @doc.options['foo'].should == 'bar'
623
+ @doc.options['baz'] = 'wick'
624
+ @doc.options['baz'].should == 'wick'
625
+ end
626
+ end
627
+ end # instance of a embedded document
628
+ end