lookout-mongo_mapper 0.11.3

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 (176) hide show
  1. data/LICENSE +20 -0
  2. data/README.rdoc +33 -0
  3. data/UPGRADES +26 -0
  4. data/bin/mmconsole +59 -0
  5. data/examples/attr_accessible.rb +22 -0
  6. data/examples/attr_protected.rb +22 -0
  7. data/examples/cache_key.rb +24 -0
  8. data/examples/custom_types.rb +24 -0
  9. data/examples/identity_map.rb +33 -0
  10. data/examples/identity_map/automatic.rb +2 -0
  11. data/examples/keys.rb +40 -0
  12. data/examples/modifiers/set.rb +25 -0
  13. data/examples/plugins.rb +38 -0
  14. data/examples/querying.rb +35 -0
  15. data/examples/safe.rb +43 -0
  16. data/examples/scopes.rb +52 -0
  17. data/examples/validating/embedded_docs.rb +29 -0
  18. data/lib/mongo_mapper.rb +94 -0
  19. data/lib/mongo_mapper/connection.rb +96 -0
  20. data/lib/mongo_mapper/document.rb +42 -0
  21. data/lib/mongo_mapper/embedded_document.rb +32 -0
  22. data/lib/mongo_mapper/exceptions.rb +30 -0
  23. data/lib/mongo_mapper/extensions/array.rb +19 -0
  24. data/lib/mongo_mapper/extensions/binary.rb +22 -0
  25. data/lib/mongo_mapper/extensions/boolean.rb +44 -0
  26. data/lib/mongo_mapper/extensions/date.rb +25 -0
  27. data/lib/mongo_mapper/extensions/float.rb +14 -0
  28. data/lib/mongo_mapper/extensions/hash.rb +14 -0
  29. data/lib/mongo_mapper/extensions/integer.rb +19 -0
  30. data/lib/mongo_mapper/extensions/kernel.rb +9 -0
  31. data/lib/mongo_mapper/extensions/nil_class.rb +18 -0
  32. data/lib/mongo_mapper/extensions/object.rb +26 -0
  33. data/lib/mongo_mapper/extensions/object_id.rb +32 -0
  34. data/lib/mongo_mapper/extensions/set.rb +20 -0
  35. data/lib/mongo_mapper/extensions/string.rb +18 -0
  36. data/lib/mongo_mapper/extensions/time.rb +28 -0
  37. data/lib/mongo_mapper/locale/en.yml +5 -0
  38. data/lib/mongo_mapper/middleware/identity_map.rb +16 -0
  39. data/lib/mongo_mapper/plugins.rb +22 -0
  40. data/lib/mongo_mapper/plugins/accessible.rb +52 -0
  41. data/lib/mongo_mapper/plugins/active_model.rb +18 -0
  42. data/lib/mongo_mapper/plugins/associations.rb +90 -0
  43. data/lib/mongo_mapper/plugins/associations/base.rb +92 -0
  44. data/lib/mongo_mapper/plugins/associations/belongs_to_association.rb +54 -0
  45. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +34 -0
  46. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +52 -0
  47. data/lib/mongo_mapper/plugins/associations/collection.rb +27 -0
  48. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +44 -0
  49. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +133 -0
  50. data/lib/mongo_mapper/plugins/associations/many_association.rb +63 -0
  51. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  52. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +118 -0
  53. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +32 -0
  54. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +24 -0
  55. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +14 -0
  56. data/lib/mongo_mapper/plugins/associations/one_as_proxy.rb +22 -0
  57. data/lib/mongo_mapper/plugins/associations/one_association.rb +48 -0
  58. data/lib/mongo_mapper/plugins/associations/one_embedded_polymorphic_proxy.rb +30 -0
  59. data/lib/mongo_mapper/plugins/associations/one_embedded_proxy.rb +44 -0
  60. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +95 -0
  61. data/lib/mongo_mapper/plugins/associations/proxy.rb +134 -0
  62. data/lib/mongo_mapper/plugins/associations/single_association.rb +46 -0
  63. data/lib/mongo_mapper/plugins/caching.rb +21 -0
  64. data/lib/mongo_mapper/plugins/callbacks.rb +29 -0
  65. data/lib/mongo_mapper/plugins/clone.rb +22 -0
  66. data/lib/mongo_mapper/plugins/dirty.rb +60 -0
  67. data/lib/mongo_mapper/plugins/document.rb +41 -0
  68. data/lib/mongo_mapper/plugins/dynamic_querying.rb +45 -0
  69. data/lib/mongo_mapper/plugins/dynamic_querying/dynamic_finder.rb +44 -0
  70. data/lib/mongo_mapper/plugins/embedded_callbacks.rb +56 -0
  71. data/lib/mongo_mapper/plugins/embedded_document.rb +53 -0
  72. data/lib/mongo_mapper/plugins/equality.rb +23 -0
  73. data/lib/mongo_mapper/plugins/identity_map.rb +128 -0
  74. data/lib/mongo_mapper/plugins/indexes.rb +13 -0
  75. data/lib/mongo_mapper/plugins/inspect.rb +16 -0
  76. data/lib/mongo_mapper/plugins/keys.rb +313 -0
  77. data/lib/mongo_mapper/plugins/keys/key.rb +61 -0
  78. data/lib/mongo_mapper/plugins/logger.rb +18 -0
  79. data/lib/mongo_mapper/plugins/modifiers.rb +134 -0
  80. data/lib/mongo_mapper/plugins/pagination.rb +16 -0
  81. data/lib/mongo_mapper/plugins/persistence.rb +69 -0
  82. data/lib/mongo_mapper/plugins/protected.rb +45 -0
  83. data/lib/mongo_mapper/plugins/querying.rb +165 -0
  84. data/lib/mongo_mapper/plugins/querying/decorator.rb +36 -0
  85. data/lib/mongo_mapper/plugins/rails.rb +58 -0
  86. data/lib/mongo_mapper/plugins/rails/active_record_association_adapter.rb +33 -0
  87. data/lib/mongo_mapper/plugins/safe.rb +28 -0
  88. data/lib/mongo_mapper/plugins/sci.rb +36 -0
  89. data/lib/mongo_mapper/plugins/scopes.rb +27 -0
  90. data/lib/mongo_mapper/plugins/serialization.rb +109 -0
  91. data/lib/mongo_mapper/plugins/timestamps.rb +22 -0
  92. data/lib/mongo_mapper/plugins/touch.rb +18 -0
  93. data/lib/mongo_mapper/plugins/userstamps.rb +18 -0
  94. data/lib/mongo_mapper/plugins/validations.rb +86 -0
  95. data/lib/mongo_mapper/railtie.rb +48 -0
  96. data/lib/mongo_mapper/railtie/database.rake +65 -0
  97. data/lib/mongo_mapper/translation.rb +10 -0
  98. data/lib/mongo_mapper/version.rb +4 -0
  99. data/lib/rails/generators/mongo_mapper/config/config_generator.rb +24 -0
  100. data/lib/rails/generators/mongo_mapper/config/templates/mongo.yml +18 -0
  101. data/lib/rails/generators/mongo_mapper/model/model_generator.rb +23 -0
  102. data/lib/rails/generators/mongo_mapper/model/templates/model.rb +13 -0
  103. data/test/_NOTE_ON_TESTING +1 -0
  104. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +64 -0
  105. data/test/functional/associations/test_belongs_to_proxy.rb +238 -0
  106. data/test/functional/associations/test_in_array_proxy.rb +349 -0
  107. data/test/functional/associations/test_many_documents_as_proxy.rb +231 -0
  108. data/test/functional/associations/test_many_documents_proxy.rb +866 -0
  109. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +239 -0
  110. data/test/functional/associations/test_many_embedded_proxy.rb +289 -0
  111. data/test/functional/associations/test_many_polymorphic_proxy.rb +303 -0
  112. data/test/functional/associations/test_one_as_proxy.rb +491 -0
  113. data/test/functional/associations/test_one_embedded_polymorphic_proxy.rb +208 -0
  114. data/test/functional/associations/test_one_embedded_proxy.rb +100 -0
  115. data/test/functional/associations/test_one_proxy.rb +383 -0
  116. data/test/functional/test_accessible.rb +198 -0
  117. data/test/functional/test_associations.rb +46 -0
  118. data/test/functional/test_binary.rb +27 -0
  119. data/test/functional/test_caching.rb +77 -0
  120. data/test/functional/test_callbacks.rb +232 -0
  121. data/test/functional/test_dirty.rb +301 -0
  122. data/test/functional/test_document.rb +282 -0
  123. data/test/functional/test_dynamic_querying.rb +75 -0
  124. data/test/functional/test_embedded_document.rb +288 -0
  125. data/test/functional/test_equality.rb +20 -0
  126. data/test/functional/test_identity_map.rb +513 -0
  127. data/test/functional/test_indexes.rb +50 -0
  128. data/test/functional/test_logger.rb +20 -0
  129. data/test/functional/test_modifiers.rb +537 -0
  130. data/test/functional/test_pagination.rb +91 -0
  131. data/test/functional/test_protected.rb +201 -0
  132. data/test/functional/test_querying.rb +935 -0
  133. data/test/functional/test_safe.rb +76 -0
  134. data/test/functional/test_sci.rb +240 -0
  135. data/test/functional/test_scopes.rb +171 -0
  136. data/test/functional/test_timestamps.rb +62 -0
  137. data/test/functional/test_touch.rb +125 -0
  138. data/test/functional/test_userstamps.rb +44 -0
  139. data/test/functional/test_validations.rb +414 -0
  140. data/test/models.rb +261 -0
  141. data/test/support/railtie.rb +4 -0
  142. data/test/support/railtie/autoloaded.rb +2 -0
  143. data/test/support/railtie/not_autoloaded.rb +3 -0
  144. data/test/support/railtie/parent.rb +3 -0
  145. data/test/test_active_model_lint.rb +18 -0
  146. data/test/test_helper.rb +93 -0
  147. data/test/unit/associations/test_base.rb +146 -0
  148. data/test/unit/associations/test_belongs_to_association.rb +29 -0
  149. data/test/unit/associations/test_many_association.rb +63 -0
  150. data/test/unit/associations/test_one_association.rb +47 -0
  151. data/test/unit/associations/test_proxy.rb +100 -0
  152. data/test/unit/serializers/test_json_serializer.rb +216 -0
  153. data/test/unit/serializers/test_xml_serializer.rb +196 -0
  154. data/test/unit/test_clone.rb +69 -0
  155. data/test/unit/test_document.rb +249 -0
  156. data/test/unit/test_dynamic_finder.rb +125 -0
  157. data/test/unit/test_embedded_document.rb +682 -0
  158. data/test/unit/test_equality.rb +38 -0
  159. data/test/unit/test_exceptions.rb +12 -0
  160. data/test/unit/test_extensions.rb +380 -0
  161. data/test/unit/test_identity_map_middleware.rb +34 -0
  162. data/test/unit/test_inspect.rb +47 -0
  163. data/test/unit/test_key.rb +205 -0
  164. data/test/unit/test_keys.rb +65 -0
  165. data/test/unit/test_mongo_mapper.rb +143 -0
  166. data/test/unit/test_pagination.rb +11 -0
  167. data/test/unit/test_plugins.rb +89 -0
  168. data/test/unit/test_rails.rb +183 -0
  169. data/test/unit/test_rails_compatibility.rb +38 -0
  170. data/test/unit/test_rails_reflect_on_association.rb +118 -0
  171. data/test/unit/test_railtie.rb +66 -0
  172. data/test/unit/test_serialization.rb +166 -0
  173. data/test/unit/test_time_zones.rb +44 -0
  174. data/test/unit/test_translation.rb +27 -0
  175. data/test/unit/test_validations.rb +562 -0
  176. metadata +285 -0
@@ -0,0 +1,75 @@
1
+ require 'test_helper'
2
+
3
+ class DynamicQueryingTest < Test::Unit::TestCase
4
+ def setup
5
+ @document = Doc do
6
+ scope :nunes, where(:last_name => 'Nunemaker')
7
+
8
+ key :first_name, String
9
+ key :last_name, String
10
+ key :age, Integer
11
+ key :date, Date
12
+ end
13
+
14
+ @doc1 = @document.create(:first_name => 'John', :last_name => 'Nunemaker', :age => 27)
15
+ @doc2 = @document.create(:first_name => 'Steve', :last_name => 'Smith', :age => 28)
16
+ @doc3 = @document.create(:first_name => 'Steph', :last_name => 'Nunemaker', :age => 26)
17
+ end
18
+
19
+ should "find document based on argument" do
20
+ @document.find_by_first_name('John').should == @doc1
21
+ @document.find_by_last_name('Nunemaker', :order => 'age desc').should == @doc1
22
+ @document.find_by_age(27).should == @doc1
23
+ end
24
+
25
+ should "not raise error" do
26
+ @document.find_by_first_name('Mongo').should be_nil
27
+ end
28
+
29
+ should "define a method for each key" do
30
+ @document.methods(false).select { |e| e =~ /^find_by_/ }.size == @document.keys.size
31
+ end
32
+
33
+ should "find document based on all arguments" do
34
+ @document.find_by_first_name_and_last_name_and_age('John', 'Nunemaker', 27).should == @doc1
35
+ end
36
+
37
+ should "not find the document if an argument is wrong" do
38
+ @document.find_by_first_name_and_last_name_and_age('John', 'Nunemaker', 28).should be_nil
39
+ end
40
+
41
+ should "find all documents based on arguments" do
42
+ docs = @document.find_all_by_last_name('Nunemaker')
43
+ docs.should be_kind_of(Array)
44
+ docs.should include(@doc1)
45
+ docs.should include(@doc3)
46
+ end
47
+
48
+ should "initialize document with given arguments" do
49
+ doc = @document.find_or_initialize_by_first_name_and_last_name('David', 'Cuadrado')
50
+ doc.should be_new
51
+ doc.first_name.should == 'David'
52
+ end
53
+
54
+ should "not initialize document if document is found" do
55
+ doc = @document.find_or_initialize_by_first_name('John')
56
+ doc.should_not be_new
57
+ end
58
+
59
+ should "create document with given arguments" do
60
+ doc = @document.find_or_create_by_first_name_and_last_name('David', 'Cuadrado')
61
+ doc.should_not be_new
62
+ doc.first_name.should == 'David'
63
+ end
64
+
65
+ should "raise error if document is not found when using !" do
66
+ lambda {
67
+ @document.find_by_first_name_and_last_name!(1,2)
68
+ }.should raise_error(MongoMapper::DocumentNotFound)
69
+ end
70
+
71
+ should "work on scopes" do
72
+ @document.nunes.find_by_first_name('Steph').should == @doc3
73
+ @document.nunes.find_all_by_first_name('Steph').should == [@doc3]
74
+ end
75
+ end
@@ -0,0 +1,288 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class EmbeddedDocumentTest < Test::Unit::TestCase
5
+ def setup
6
+ @klass = Doc('Person') do
7
+ key :name, String
8
+ end
9
+
10
+ @pet_klass = EDoc('Pet') do
11
+ key :name, String
12
+ end
13
+
14
+ @klass.many :pets, :class => @pet_klass
15
+
16
+ @address_class = EDoc('Address') do
17
+ key :city, String
18
+ key :state, String
19
+ end
20
+ end
21
+
22
+ context "Saving a document with a key that is an embedded document" do
23
+ setup do
24
+ @klass.key :foo, @address_class
25
+ end
26
+
27
+ should "embed embedded document" do
28
+ address = @address_class.new(:city => 'South Bend', :state => 'IN')
29
+ doc = @klass.create(:foo => address)
30
+ doc.foo.city.should == 'South Bend'
31
+ doc.foo.state.should == 'IN'
32
+
33
+ doc = doc.reload
34
+ doc.foo.city.should == 'South Bend'
35
+ doc.foo.state.should == 'IN'
36
+ end
37
+
38
+ should "assign _parent_document and _root_document" do
39
+ address = @address_class.new(:city => 'South Bend', :state => 'IN')
40
+ address._parent_document.should be_nil
41
+ doc = @klass.create(:foo => address)
42
+ address._parent_document.should be(doc)
43
+ address._root_document.should be(doc)
44
+ end
45
+
46
+ should "assign _parent_document and _root_document when loading" do
47
+ address = @address_class.new(:city => 'South Bend', :state => 'IN')
48
+ doc = @klass.create(:foo => address)
49
+ doc.reload
50
+ doc.foo._parent_document.should be(doc)
51
+ doc.foo._root_document.should be(doc)
52
+ end
53
+ end
54
+
55
+ should "correctly instantiate single collection inherited embedded documents" do
56
+ document = Doc('Foo') do
57
+ key :message, Message
58
+ end
59
+
60
+ doc1 = document.create(:message => Enter.new)
61
+ doc1.reload.message.class.should be(Enter)
62
+ end
63
+
64
+ context "new? (embedded key)" do
65
+ setup do
66
+ @klass.key :foo, @address_class
67
+ end
68
+
69
+ should "be true until document is created" do
70
+ address = @address_class.new(:city => 'South Bend', :state => 'IN')
71
+ doc = @klass.new(:foo => address)
72
+ address.new?.should be_true
73
+ end
74
+
75
+ should "be false after document is saved" do
76
+ address = @address_class.new(:city => 'South Bend', :state => 'IN')
77
+ doc = @klass.new(:foo => address)
78
+ doc.save
79
+ doc.foo.new?.should be_false
80
+ end
81
+
82
+ should "be false when loaded from database" do
83
+ address = @address_class.new(:city => 'South Bend', :state => 'IN')
84
+ doc = @klass.new(:foo => address)
85
+ doc.save
86
+
87
+ doc.reload
88
+ doc.foo.new?.should be_false
89
+ end
90
+ end
91
+
92
+ context "new? (embedded many association)" do
93
+ setup do
94
+ @doc = @klass.new(:pets => [{:name => 'poo bear'}])
95
+ end
96
+
97
+ should "be true until document is saved" do
98
+ @doc.should be_new
99
+ @doc.pets.first.should be_new
100
+ end
101
+
102
+ should "be false after document is saved" do
103
+ @doc.save
104
+ @doc.pets.first.should_not be_new
105
+ end
106
+
107
+ should "be false when loaded from database" do
108
+ @doc.save
109
+ @doc.pets.first.should_not be_new
110
+ @doc.reload
111
+ @doc.pets.first.should_not be_new
112
+ end
113
+
114
+ should "be true until existing document is saved" do
115
+ @doc.save
116
+ pet = @doc.pets.build(:name => 'Rasmus')
117
+ pet.new?.should be_true
118
+ @doc.save
119
+ pet.new?.should be_false
120
+ end
121
+ end
122
+
123
+ context "new? (nested embedded many association)" do
124
+ setup do
125
+ @pet_klass.many :addresses, :class=> @address_class
126
+ @doc = @klass.new
127
+ @doc.pets.build(:name => 'Rasmus')
128
+ @doc.save
129
+ end
130
+
131
+ should "be true until existing document is saved" do
132
+ address = @doc.pets.first.addresses.build(:city => 'Holland', :state => 'MI')
133
+ address.new?.should be_true
134
+ @doc.save
135
+ address.new?.should be_false
136
+ end
137
+ end
138
+
139
+ context "new? (embedded one association)" do
140
+ setup do
141
+ @klass.one :address, :class => @address_class
142
+ @doc = @klass.new
143
+ end
144
+
145
+ should "be true until existing document is saved" do
146
+ @doc.save
147
+ @doc.build_address(:city => 'Holland', :state => 'MI')
148
+ @doc.address.new?.should be_true
149
+ @doc.save
150
+ @doc.address.new?.should be_false
151
+ end
152
+ end
153
+
154
+ context "new? (nested embedded one association)" do
155
+ setup do
156
+ @pet_klass.one :address, :class => @address_class
157
+ @doc = @klass.new
158
+ @doc.pets.build(:name => 'Rasmus')
159
+ @doc.save
160
+ end
161
+
162
+ should "be true until existing document is saved" do
163
+ address = @doc.pets.first.build_address(:city => 'Holland', :stats => 'MI')
164
+ address.new?.should be_true
165
+ @doc.save
166
+ address.new?.should be_false
167
+ end
168
+ end
169
+
170
+ context "#destroyed?" do
171
+ setup do
172
+ @doc = @klass.create(:pets => [@pet_klass.new(:name => 'sparky')])
173
+ end
174
+
175
+ should "be false if root document is not destroyed" do
176
+ @doc.should_not be_destroyed
177
+ @doc.pets.first.should_not be_destroyed
178
+ end
179
+
180
+ should "be true if root document is destroyed" do
181
+ @doc.destroy
182
+ @doc.should be_destroyed
183
+ @doc.pets.first.should be_destroyed
184
+ end
185
+ end
186
+
187
+ context "#persisted?" do
188
+ setup do
189
+ @doc = @klass.new(:name => 'persisted doc', :pets => [@pet_klass.new(:name => 'persisted pet')])
190
+ end
191
+
192
+ should "be false if new" do
193
+ @doc.pets.first.should_not be_persisted
194
+ end
195
+
196
+ should "be false if destroyed" do
197
+ @doc.save
198
+ @doc.destroy
199
+ @doc.pets.first.should be_destroyed
200
+ @doc.pets.first.should_not be_persisted
201
+ end
202
+
203
+ should "be true if not new or destroyed" do
204
+ @doc.save
205
+ @doc.pets.first.should be_persisted
206
+ end
207
+ end
208
+
209
+ should "be able to save" do
210
+ person = @klass.create
211
+
212
+ pet = @pet_klass.new(:name => 'sparky')
213
+ person.pets << pet
214
+ pet.should be_new
215
+ pet.save
216
+ pet.should_not be_new
217
+
218
+ person.reload
219
+ person.pets.first.should == pet
220
+ end
221
+
222
+ should "be able to save!" do
223
+ person = @klass.create
224
+
225
+ pet = @pet_klass.new(:name => 'sparky')
226
+ person.pets << pet
227
+ pet.should be_new
228
+
229
+ person.expects(:save!)
230
+ pet.save!
231
+ end
232
+
233
+ should "be able to dynamically add new keys and save" do
234
+ person = @klass.create
235
+
236
+ pet = @pet_klass.new(:name => 'sparky', :crazy_key => 'crazy')
237
+ person.pets << pet
238
+ pet.save
239
+
240
+ person.reload
241
+ person.pets.first.crazy_key.should == 'crazy'
242
+ end
243
+
244
+ should "be able to update_attribute" do
245
+ pet = @pet_klass.new(:name => 'sparky')
246
+ person = @klass.create(:pets => [pet])
247
+ person.reload
248
+ pet = person.pets.first
249
+
250
+ pet.update_attribute('name', 'koda').should be_true
251
+ person.reload
252
+ person.pets.first._id.should == pet._id
253
+ person.pets.first.name.should == 'koda'
254
+ end
255
+
256
+ should "be able to update_attributes" do
257
+ pet = @pet_klass.new(:name => 'sparky')
258
+ person = @klass.create(:pets => [pet])
259
+ person.reload
260
+ pet = person.pets.first
261
+
262
+ pet.update_attributes(:name => 'koda').should be_true
263
+ person.reload
264
+ person.pets.first._id.should == pet._id
265
+ person.pets.first.name.should == 'koda'
266
+ end
267
+
268
+ should "be able to update_attributes!" do
269
+ person = @klass.create(:pets => [@pet_klass.new(:name => 'sparky')])
270
+ person.reload
271
+ pet = person.pets.first
272
+
273
+ attributes = {:name => 'koda'}
274
+ pet.expects(:attributes=).with(attributes)
275
+ pet.expects(:save!)
276
+ pet.update_attributes!(attributes)
277
+ end
278
+
279
+ should "have database instance method that is equal to root document" do
280
+ person = @klass.create(:pets => [@pet_klass.new(:name => 'sparky')])
281
+ person.pets.first.database.should == person.database
282
+ end
283
+
284
+ should "have collection instance method that is equal to root document" do
285
+ person = @klass.create(:pets => [@pet_klass.new(:name => 'sparky')])
286
+ person.pets.first.collection.name.should == person.collection.name
287
+ end
288
+ end
@@ -0,0 +1,20 @@
1
+ require 'test_helper'
2
+
3
+ class EqualityTest < Test::Unit::TestCase
4
+ context "Case equality" do
5
+ setup do
6
+ @person = Doc()
7
+ @address = Doc()
8
+
9
+ @person.one :address, :class => @address, :foreign_key => :person_id
10
+ @address.belongs_to :person, :class => @person
11
+ end
12
+
13
+ should "work with proxies" do
14
+ person = @person.create!
15
+ address = @address.create!(:person => person)
16
+ @person.should === address.person
17
+ @address.should === person.address
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,513 @@
1
+ require 'test_helper'
2
+
3
+ class IdentityMapTest < Test::Unit::TestCase
4
+ def assert_in_map(*resources)
5
+ [resources].flatten.each do |resource|
6
+ resource.identity_map.keys.should include(resource._id)
7
+ mapped_resource = resource.identity_map[resource._id]
8
+ resource.should equal(mapped_resource)
9
+ end
10
+ end
11
+
12
+ def assert_not_in_map(*resources)
13
+ [resources].flatten.each do |resource|
14
+ resource.identity_map.keys.should_not include(resource._id)
15
+ end
16
+ end
17
+
18
+ def expect_no_queries
19
+ Mongo::Collection.any_instance.expects(:find_one).never
20
+ Mongo::Collection.any_instance.expects(:find).never
21
+ end
22
+
23
+ def expects_one_query
24
+ Mongo::Collection.any_instance.expects(:find_one).once.returns({})
25
+ end
26
+
27
+ context "Document" do
28
+ setup do
29
+ MongoMapper::Plugins::IdentityMap.models.clear
30
+
31
+ @person_class = Doc('Person') do
32
+ plugin MongoMapper::Plugins::IdentityMap
33
+
34
+ key :name, String
35
+ end
36
+
37
+ @post_class = Doc('Post') do
38
+ plugin MongoMapper::Plugins::IdentityMap
39
+
40
+ key :title, String
41
+ key :person_id, ObjectId
42
+ end
43
+
44
+ @post_class.belongs_to :person, :class => @person_class
45
+ @person_class.many :posts, :class => @post_class
46
+
47
+ @post_class.identity_map_on
48
+ @person_class.identity_map_on
49
+ MongoMapper::Plugins::IdentityMap.clear
50
+ end
51
+
52
+ should "track identity mapped models" do
53
+ MongoMapper::Plugins::IdentityMap.models.should == [@person_class, @post_class].to_set
54
+ end
55
+
56
+ should "be able to clear the map of all models" do
57
+ person = @person_class.create(:name => 'John')
58
+ post = @post_class.create(:title => 'IM 4eva')
59
+ assert_in_map(person, post)
60
+
61
+ MongoMapper::Plugins::IdentityMap.clear
62
+
63
+ assert_not_in_map(person, post)
64
+
65
+ [@person_class, @post_class].each { |klass| klass.identity_map.should == {} }
66
+ end
67
+
68
+ context "IM on off status" do
69
+ teardown do
70
+ @post_class.identity_map_on
71
+ @person_class.identity_map_on
72
+ end
73
+
74
+ should "default identity map status to on" do
75
+ Doc { plugin MongoMapper::Plugins::IdentityMap }.identity_map_status.should be_true
76
+ end
77
+
78
+ should "be true if on" do
79
+ @post_class.identity_map_on
80
+ @post_class.should be_identity_map_on
81
+ @post_class.should_not be_identity_map_off
82
+ end
83
+
84
+ should "be false if off" do
85
+ @post_class.identity_map_off
86
+ @post_class.should be_identity_map_off
87
+ @post_class.should_not be_identity_map_on
88
+ end
89
+
90
+ should "not share with other classes" do
91
+ @post_class.identity_map_off
92
+ @person_class.identity_map_on
93
+ @post_class.identity_map_status.should_not == @person_class.identity_map_status
94
+ end
95
+ end
96
+
97
+ should "default identity map to hash" do
98
+ Doc { plugin MongoMapper::Plugins::IdentityMap }.identity_map.should == {}
99
+ end
100
+
101
+ should "add key to map when saved" do
102
+ person = @person_class.new
103
+ assert_not_in_map(person)
104
+ person.save.should be_true
105
+ assert_in_map(person)
106
+ end
107
+
108
+ should "allow saving with options" do
109
+ person = @person_class.new
110
+ assert_nothing_raised do
111
+ person.save(:validate => false).should be_true
112
+ end
113
+ end
114
+
115
+ should "remove key from map when deleted" do
116
+ person = @person_class.create(:name => 'Fred')
117
+ assert_in_map(person)
118
+ person.destroy
119
+ assert_not_in_map(person)
120
+ end
121
+
122
+ context "reload" do
123
+ setup do
124
+ @person = @person_class.create(:name => 'Fred')
125
+ end
126
+
127
+ should "remove object from identity and re-query" do
128
+ assert_in_map(@person)
129
+ expects_one_query
130
+ @person.reload
131
+ end
132
+
133
+ should "add object back into map" do
134
+ assert_in_map(@person)
135
+ before_reload = @person
136
+ @person.reload.should equal(before_reload)
137
+ assert_in_map(@person)
138
+ end
139
+ end
140
+
141
+ context "#load" do
142
+ setup do
143
+ @id = BSON::ObjectId.new
144
+ end
145
+
146
+ should "add document to map" do
147
+ loaded = @person_class.load({'_id' => @id, 'name' => 'Frank'})
148
+ assert_in_map(loaded)
149
+ end
150
+
151
+ should "return document if already in map" do
152
+ first_load = @person_class.load({'_id' => @id, 'name' => 'Frank'})
153
+ @person_class.identity_map.expects(:[]=).never
154
+ second_load = @person_class.load({'_id' => @id, 'name' => 'Frank'})
155
+ first_load.should equal(second_load)
156
+ end
157
+ end
158
+
159
+ context "#find (with one id)" do
160
+ context "for object not in map" do
161
+ setup do
162
+ @person = @person_class.create(:name => 'Fred')
163
+ @person_class.identity_map.clear
164
+ end
165
+
166
+ should "query the database" do
167
+ expects_one_query
168
+ @person_class.find(@person.id)
169
+ end
170
+
171
+ should "add object to map" do
172
+ assert_not_in_map(@person)
173
+ found_person = @person_class.find(@person.id)
174
+ assert_in_map(found_person)
175
+ end
176
+
177
+ should "return nil if not found " do
178
+ @person_class.find(1234).should be_nil
179
+ end
180
+ end
181
+
182
+ context "for object in map" do
183
+ setup do
184
+ @person = @person_class.create(:name => 'Fred')
185
+ end
186
+
187
+ should "not query database" do
188
+ expect_no_queries
189
+ @person_class.find(@person.id)
190
+ end
191
+
192
+ should "return exact object" do
193
+ assert_in_map(@person)
194
+ found_person = @person_class.find(@person.id)
195
+ found_person.should equal(@person)
196
+ end
197
+ end
198
+ end
199
+
200
+ context "#find (with one id and options)" do
201
+ setup do
202
+ @person = @person_class.create(:name => 'Fred')
203
+ @post1 = @person.posts.create(:title => 'I Love Mongo')
204
+ @post2 = @person.posts.create(:title => 'Migrations Suck!')
205
+ end
206
+
207
+ # There are times when even though the id matches, other criteria doesn't
208
+ # so we need to do the query to ensure that when criteria doesn't match
209
+ # the document is in fact not found.
210
+ #
211
+ # I'm open to not making this query if someone can figure out reliable
212
+ # way to check if document matches criteria without querying.
213
+ should "query the database" do
214
+ assert_in_map(@post1)
215
+ expects_one_query
216
+ @person.posts.find(@post1.id)
217
+ end
218
+
219
+ should "return exact object" do
220
+ assert_in_map(@post1)
221
+ @person.posts.find(@post1.id)
222
+ assert_in_map(@post1)
223
+ end
224
+
225
+ should "return nil if not found " do
226
+ @person.posts.find(1234).should be_nil
227
+ end
228
+ end
229
+
230
+ context "#find (with multiple ids)" do
231
+ should "add all documents to map" do
232
+ person1 = @person_class.create(:name => 'Fred')
233
+ person2 = @person_class.create(:name => 'Bill')
234
+ person3 = @person_class.create(:name => 'Jesse')
235
+ @person_class.identity_map.clear
236
+
237
+ people = @person_class.find(person1.id, person2.id, person3.id)
238
+ assert_in_map(people)
239
+ end
240
+
241
+ should "add missing documents to map and return existing ones" do
242
+ person1 = @person_class.create(:name => 'Fred')
243
+ @person_class.identity_map.clear
244
+ person2 = @person_class.create(:name => 'Bill')
245
+ person3 = @person_class.create(:name => 'Jesse')
246
+
247
+ assert_not_in_map(person1)
248
+ assert_in_map(person2, person3)
249
+
250
+ people = @person_class.find(person1.id, person2.id, person3.id)
251
+ assert_in_map(people.first) # making sure one that wasn't mapped now is
252
+ assert_in_map(person2, person3)
253
+ end
254
+ end
255
+
256
+ context "#first" do
257
+ context "for object not in map" do
258
+ setup do
259
+ @person = @person_class.create(:name => 'Fred')
260
+ @person_class.identity_map.clear
261
+ end
262
+
263
+ should "query the database" do
264
+ expects_one_query
265
+ @person_class.first(:_id => @person.id)
266
+ end
267
+
268
+ should "add object to map" do
269
+ assert_not_in_map(@person)
270
+ found_person = @person_class.first(:_id => @person.id)
271
+ assert_in_map(found_person)
272
+ end
273
+
274
+ should "return nil if not found" do
275
+ @person_class.first(:name => 'Bill').should be_nil
276
+ end
277
+ end
278
+
279
+ context "for object in map" do
280
+ setup do
281
+ @person = @person_class.create(:name => 'Fred')
282
+ end
283
+
284
+ should "not query database" do
285
+ expect_no_queries
286
+ @person_class.first(:_id => @person.id)
287
+ end
288
+
289
+ should "return exact object" do
290
+ assert_in_map(@person)
291
+ found_person = @person_class.first(:_id => @person.id)
292
+ found_person.should equal(@person)
293
+ end
294
+ end
295
+ end
296
+
297
+ context "#all" do
298
+ should "add all documents to map" do
299
+ person1 = @person_class.create(:name => 'Fred')
300
+ person2 = @person_class.create(:name => 'Bill')
301
+ person3 = @person_class.create(:name => 'Jesse')
302
+ @person_class.identity_map.clear
303
+
304
+ people = @person_class.all(:_id => [person1.id, person2.id, person3.id])
305
+ assert_in_map(people)
306
+ end
307
+
308
+ should "add missing documents to map and return existing ones" do
309
+ person1 = @person_class.create(:name => 'Fred')
310
+ @person_class.identity_map.clear
311
+ person2 = @person_class.create(:name => 'Bill')
312
+ person3 = @person_class.create(:name => 'Jesse')
313
+
314
+ assert_not_in_map(person1)
315
+ assert_in_map(person2, person3)
316
+
317
+ people = @person_class.all(:_id => [person1.id, person2.id, person3.id])
318
+ # people.first is making sure one that wasn't mapped now is
319
+ assert_in_map(people.first, person2, person3)
320
+ end
321
+ end
322
+
323
+ context "#find_by_id" do
324
+ setup do
325
+ @person = @person_class.create(:name => 'Bill')
326
+ end
327
+
328
+ should "return nil for document id not found in collection" do
329
+ assert_in_map(@person)
330
+ @person_class.find_by_id(BSON::ObjectId.new).should be_nil
331
+ end
332
+ end
333
+
334
+ context "querying and selecting certain fields" do
335
+ setup do
336
+ @person = @person_class.create(:name => 'Bill')
337
+ @person_class.identity_map.clear
338
+ end
339
+
340
+ should "not add to map" do
341
+ assert_not_in_map(@person)
342
+ @person_class.first(:_id => @person.id, :select => 'name').should == @person
343
+ @person_class.first(:_id => @person.id, 'fields' => ['name']).should == @person
344
+ @person_class.last(:_id => @person.id, :select => 'name', :order => 'name').should == @person
345
+ @person_class.fields(:name).find(@person.id).should == @person
346
+ @person_class.all(:_id => @person.id, :select => 'name').should == [@person]
347
+ assert_not_in_map(@person)
348
+ end
349
+
350
+ should "return nil if not found" do
351
+ @person_class.fields(:name).find(BSON::ObjectId.new).should be_nil
352
+ end
353
+ end
354
+
355
+ context "single collection inherited models" do
356
+ setup do
357
+ class ::Item
358
+ include MongoMapper::Document
359
+ plugin MongoMapper::Plugins::IdentityMap
360
+
361
+ key :title, String
362
+ key :parent_id, ObjectId
363
+
364
+ belongs_to :parent, :class_name => 'Item'
365
+ one :blog, :class_name => 'Blog', :foreign_key => 'parent_id'
366
+ end
367
+ Item.collection.remove
368
+
369
+ class ::Blog < ::Item; end
370
+
371
+ class ::BlogPost < ::Item
372
+ key :blog_id, ObjectId
373
+ belongs_to :blog
374
+ end
375
+ end
376
+
377
+ teardown do
378
+ Object.send :remove_const, 'Item' if defined?(::Item)
379
+ Object.send :remove_const, 'Blog' if defined?(::Blog)
380
+ Object.send :remove_const, 'BlogPost' if defined?(::BlogPost)
381
+ end
382
+
383
+ should "share the same identity map" do
384
+ blog = Blog.create(:title => 'Jill')
385
+ assert_in_map(blog)
386
+ Item.identity_map.should equal(Blog.identity_map)
387
+ end
388
+
389
+ should "not query when finding by _id and _type" do
390
+ blog = Blog.create(:title => 'Blog')
391
+ post = BlogPost.create(:title => 'Mongo Rocks', :blog => blog)
392
+ Item.identity_map.clear
393
+
394
+ blog = Item.find(blog.id)
395
+ post = Item.find(post.id)
396
+ assert_in_map(blog, post)
397
+
398
+ expect_no_queries
399
+ post.blog
400
+ Blog.find(blog.id)
401
+ end
402
+
403
+ should "load from map when using parent collection inherited class" do
404
+ blog = Blog.create(:title => 'Jill')
405
+ Item.find(blog.id).should equal(blog)
406
+ end
407
+
408
+ should "work correctly with belongs to proxy" do
409
+ root = Item.create(:title => 'Root')
410
+ assert_in_map(root)
411
+
412
+ blog = Blog.create(:title => 'Jill', :parent => root)
413
+ assert_in_map(blog)
414
+ root.should equal(blog.parent.target)
415
+ end
416
+
417
+ should "work correctly with one proxy" do
418
+ blog = Blog.create(:title => 'Jill')
419
+ assert_in_map(blog)
420
+
421
+ root = Item.create(:title => 'Root', :blog => blog)
422
+ assert_in_map(root)
423
+ blog.should equal(root.blog.target)
424
+ end
425
+
426
+ should "work correctly with one proxy create" do
427
+ root = Item.create(:title => 'Root')
428
+ blog = root.create_blog(:title => 'Blog')
429
+ blog.parent.should equal(root)
430
+ end
431
+ end
432
+
433
+ context "without identity map" do
434
+ should "not add to map on save" do
435
+ @post_class.without_identity_map do
436
+ post = @post_class.create(:title => 'Bill')
437
+ assert_not_in_map(post)
438
+ end
439
+ end
440
+
441
+ should "not remove from map on delete" do
442
+ post = @post_class.create(:title => 'Bill')
443
+ assert_in_map(post)
444
+
445
+ @post_class.without_identity_map do
446
+ post.destroy
447
+ end
448
+
449
+ assert_in_map(post)
450
+ end
451
+
452
+ should "not add to map when loading" do
453
+ @post_class.without_identity_map do
454
+ post = @post_class.load({'_id' => BSON::ObjectId.new, 'title' => 'Awesome!'})
455
+ assert_not_in_map(post)
456
+ end
457
+ end
458
+
459
+ should "not load from map when loading" do
460
+ post = @post_class.create(:title => 'Awesome!')
461
+
462
+ @post_class.without_identity_map do
463
+ loaded = @post_class.load('_id' => post._id, 'title' => 'Awesome!')
464
+ loaded.should_not equal(post)
465
+ end
466
+ end
467
+
468
+ should "not load attributes from map when finding" do
469
+ post = @post_class.create(:title => 'Awesome!')
470
+ post.title = 'Temporary'
471
+ @post_class.without_identity_map do
472
+ @post_class.find(post.id).title.should == 'Awesome!'
473
+ end
474
+ end
475
+
476
+ context "all" do
477
+ should "not add to map" do
478
+ @post_class.without_identity_map do
479
+ post1 = @post_class.create(:title => 'Foo')
480
+ post2 = @post_class.create(:title => 'Bar')
481
+ @post_class.identity_map.clear
482
+
483
+ assert_not_in_map(@post_class.all)
484
+ end
485
+ end
486
+ end
487
+
488
+ context "first" do
489
+ should "not add to map" do
490
+ @post_class.without_identity_map do
491
+ post1 = @post_class.create(:title => 'Foo')
492
+ post2 = @post_class.create(:title => 'Bar')
493
+ @post_class.identity_map.clear
494
+
495
+ assert_not_in_map(@post_class.first)
496
+ end
497
+ end
498
+ end
499
+
500
+ context "last" do
501
+ should "not add to map" do
502
+ @post_class.without_identity_map do
503
+ post1 = @post_class.create(:title => 'Foo')
504
+ post2 = @post_class.create(:title => 'Bar')
505
+ @post_class.identity_map.clear
506
+
507
+ assert_not_in_map(@post_class.last(:order => 'title'))
508
+ end
509
+ end
510
+ end
511
+ end
512
+ end
513
+ end