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