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