mongo_mapper-rails3 0.7.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (94) hide show
  1. data/.gitignore +10 -0
  2. data/Gemfile +15 -0
  3. data/LICENSE +20 -0
  4. data/README.rdoc +60 -0
  5. data/Rakefile +58 -0
  6. data/VERSION +1 -0
  7. data/bin/mmconsole +60 -0
  8. data/lib/mongo_mapper.rb +131 -0
  9. data/lib/mongo_mapper/document.rb +439 -0
  10. data/lib/mongo_mapper/embedded_document.rb +68 -0
  11. data/lib/mongo_mapper/plugins.rb +30 -0
  12. data/lib/mongo_mapper/plugins/associations.rb +106 -0
  13. data/lib/mongo_mapper/plugins/associations/base.rb +123 -0
  14. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +30 -0
  15. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +25 -0
  16. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  17. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +50 -0
  18. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +141 -0
  19. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  20. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +120 -0
  21. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  22. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  23. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  24. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +68 -0
  25. data/lib/mongo_mapper/plugins/associations/proxy.rb +119 -0
  26. data/lib/mongo_mapper/plugins/callbacks.rb +87 -0
  27. data/lib/mongo_mapper/plugins/clone.rb +14 -0
  28. data/lib/mongo_mapper/plugins/descendants.rb +17 -0
  29. data/lib/mongo_mapper/plugins/dirty.rb +120 -0
  30. data/lib/mongo_mapper/plugins/equality.rb +24 -0
  31. data/lib/mongo_mapper/plugins/identity_map.rb +124 -0
  32. data/lib/mongo_mapper/plugins/inspect.rb +15 -0
  33. data/lib/mongo_mapper/plugins/keys.rb +310 -0
  34. data/lib/mongo_mapper/plugins/logger.rb +19 -0
  35. data/lib/mongo_mapper/plugins/pagination.rb +26 -0
  36. data/lib/mongo_mapper/plugins/pagination/proxy.rb +72 -0
  37. data/lib/mongo_mapper/plugins/protected.rb +46 -0
  38. data/lib/mongo_mapper/plugins/rails.rb +46 -0
  39. data/lib/mongo_mapper/plugins/serialization.rb +50 -0
  40. data/lib/mongo_mapper/plugins/validations.rb +88 -0
  41. data/lib/mongo_mapper/query.rb +130 -0
  42. data/lib/mongo_mapper/support.rb +217 -0
  43. data/lib/mongo_mapper/support/descendant_appends.rb +46 -0
  44. data/lib/mongo_mapper/support/find.rb +77 -0
  45. data/mongo_mapper-rails3.gemspec +208 -0
  46. data/performance/read_write.rb +52 -0
  47. data/specs.watchr +51 -0
  48. data/test/NOTE_ON_TESTING +1 -0
  49. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +63 -0
  50. data/test/functional/associations/test_belongs_to_proxy.rb +101 -0
  51. data/test/functional/associations/test_in_array_proxy.rb +321 -0
  52. data/test/functional/associations/test_many_documents_as_proxy.rb +229 -0
  53. data/test/functional/associations/test_many_documents_proxy.rb +453 -0
  54. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +176 -0
  55. data/test/functional/associations/test_many_embedded_proxy.rb +256 -0
  56. data/test/functional/associations/test_many_polymorphic_proxy.rb +302 -0
  57. data/test/functional/associations/test_one_proxy.rb +161 -0
  58. data/test/functional/test_associations.rb +44 -0
  59. data/test/functional/test_binary.rb +27 -0
  60. data/test/functional/test_callbacks.rb +81 -0
  61. data/test/functional/test_dirty.rb +163 -0
  62. data/test/functional/test_document.rb +1244 -0
  63. data/test/functional/test_embedded_document.rb +125 -0
  64. data/test/functional/test_identity_map.rb +508 -0
  65. data/test/functional/test_logger.rb +20 -0
  66. data/test/functional/test_modifiers.rb +252 -0
  67. data/test/functional/test_pagination.rb +93 -0
  68. data/test/functional/test_protected.rb +161 -0
  69. data/test/functional/test_string_id_compatibility.rb +67 -0
  70. data/test/functional/test_validations.rb +329 -0
  71. data/test/models.rb +232 -0
  72. data/test/support/custom_matchers.rb +55 -0
  73. data/test/support/timing.rb +16 -0
  74. data/test/test_helper.rb +59 -0
  75. data/test/unit/associations/test_base.rb +207 -0
  76. data/test/unit/associations/test_proxy.rb +105 -0
  77. data/test/unit/serializers/test_json_serializer.rb +189 -0
  78. data/test/unit/test_descendant_appends.rb +71 -0
  79. data/test/unit/test_document.rb +231 -0
  80. data/test/unit/test_dynamic_finder.rb +123 -0
  81. data/test/unit/test_embedded_document.rb +663 -0
  82. data/test/unit/test_keys.rb +169 -0
  83. data/test/unit/test_lint.rb +8 -0
  84. data/test/unit/test_mongo_mapper.rb +125 -0
  85. data/test/unit/test_pagination.rb +160 -0
  86. data/test/unit/test_plugins.rb +51 -0
  87. data/test/unit/test_query.rb +334 -0
  88. data/test/unit/test_rails.rb +123 -0
  89. data/test/unit/test_rails_compatibility.rb +57 -0
  90. data/test/unit/test_serialization.rb +51 -0
  91. data/test/unit/test_support.rb +362 -0
  92. data/test/unit/test_time_zones.rb +39 -0
  93. data/test/unit/test_validations.rb +557 -0
  94. metadata +344 -0
@@ -0,0 +1,176 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class ManyEmbeddedPolymorphicProxyTest < Test::Unit::TestCase
5
+ def setup
6
+ Catalog.collection.remove
7
+ TrModels::Fleet.collection.remove
8
+ end
9
+
10
+ should "default reader to empty array" do
11
+ catalog = Catalog.new
12
+ catalog.medias.should == []
13
+ end
14
+
15
+ should "allow adding to association like it was an array" do
16
+ catalog = Catalog.new
17
+ catalog.medias << Video.new
18
+ catalog.medias.push Video.new
19
+ catalog.medias.size.should == 2
20
+ end
21
+
22
+ should "be able to replace the association" do
23
+ catalog = Catalog.new
24
+ catalog.medias = [Video.new('file' => 'video.mpg', 'length' => 3600)]
25
+ catalog.save.should be_true
26
+
27
+ catalog = catalog.reload
28
+ catalog.medias.size.should == 1
29
+ catalog.medias[0].file.should == 'video.mpg'
30
+ catalog.medias[0].new?.should == false
31
+ end
32
+
33
+ context "count" do
34
+ should "default to 0" do
35
+ Catalog.new.medias.count.should == 0
36
+ end
37
+
38
+ should 'return correct count if any are embedded' do
39
+ catalog = Catalog.new
40
+ catalog.medias = [
41
+ Video.new('file' => 'video.mpg', 'length' => 3600),
42
+ Music.new('file' => 'music.mp3', 'bitrate' => '128kbps'),
43
+ Image.new('file' => 'image.png', 'width' => 800, 'height' => 600)
44
+ ]
45
+ catalog.medias.count.should == 3
46
+ catalog.save.should be_true
47
+ catalog.reload
48
+ catalog.medias.count.should == 3
49
+ end
50
+ end
51
+
52
+ should "store different associations" do
53
+ catalog = Catalog.new
54
+ catalog.medias = [
55
+ Video.new('file' => 'video.mpg', 'length' => 3600),
56
+ Music.new('file' => 'music.mp3', 'bitrate' => '128kbps'),
57
+ Image.new('file' => 'image.png', 'width' => 800, 'height' => 600)
58
+ ]
59
+ catalog.save.should be_true
60
+
61
+ catalog = catalog.reload
62
+ catalog.medias.size.should == 3
63
+ catalog.medias[0].file.should == 'video.mpg'
64
+ catalog.medias[0].length.should == 3600
65
+ catalog.medias[1].file.should == 'music.mp3'
66
+ catalog.medias[1].bitrate.should == "128kbps"
67
+ catalog.medias[2].file.should == 'image.png'
68
+ catalog.medias[2].width.should == 800
69
+ catalog.medias[2].height.should == 600
70
+ end
71
+
72
+ context "With modularized models" do
73
+ should "set associations correctly" do
74
+ fleet_attributes = {
75
+ 'name' => 'My Fleet',
76
+ 'transports' => [
77
+ {'_type' => 'TrModels::Ambulance', 'license_plate' => 'GGG123', 'icu' => true},
78
+ {'_type' => 'TrModels::Car', 'license_plate' => 'ABC123', 'model' => 'VW Golf', 'year' => 2001},
79
+ {'_type' => 'TrModels::Car', 'license_plate' => 'DEF123', 'model' => 'Honda Accord', 'year' => 2008},
80
+ ]
81
+ }
82
+
83
+ fleet = TrModels::Fleet.new(fleet_attributes)
84
+ fleet.transports.size.should == 3
85
+ fleet.transports[0].class.should == TrModels::Ambulance
86
+ fleet.transports[0].license_plate.should == 'GGG123'
87
+ fleet.transports[0].icu.should be_true
88
+ fleet.transports[1].class.should == TrModels::Car
89
+ fleet.transports[1].license_plate.should == 'ABC123'
90
+ fleet.transports[1].model.should == 'VW Golf'
91
+ fleet.transports[1].year.should == 2001
92
+ fleet.transports[2].class.should == TrModels::Car
93
+ fleet.transports[2].license_plate.should == 'DEF123'
94
+ fleet.transports[2].model.should == 'Honda Accord'
95
+ fleet.transports[2].year.should == 2008
96
+ fleet.save.should be_true
97
+
98
+ fleet = fleet.reload
99
+ fleet.transports.size.should == 3
100
+ fleet.transports[0].license_plate.should == 'GGG123'
101
+ fleet.transports[0].icu.should be_true
102
+ fleet.transports[1].license_plate.should == 'ABC123'
103
+ fleet.transports[1].model.should == 'VW Golf'
104
+ fleet.transports[1].year.should == 2001
105
+ fleet.transports[2].license_plate.should == 'DEF123'
106
+ fleet.transports[2].model.should == 'Honda Accord'
107
+ fleet.transports[2].year.should == 2008
108
+ end
109
+
110
+ should "default reader to empty array" do
111
+ fleet = TrModels::Fleet.new
112
+ fleet.transports.should == []
113
+ end
114
+
115
+ should "allow adding to association like it was an array" do
116
+ fleet = TrModels::Fleet.new
117
+ fleet.transports << TrModels::Car.new
118
+ fleet.transports.push TrModels::Bus.new
119
+ fleet.transports.size.should == 2
120
+ end
121
+
122
+ should "be able to replace the association" do
123
+ fleet = TrModels::Fleet.new
124
+ fleet.transports = [TrModels::Car.new('license_plate' => 'DCU2013', 'model' => 'Honda Civic')]
125
+ fleet.save.should be_true
126
+
127
+ fleet = fleet.reload
128
+ fleet.transports.size.should == 1
129
+ fleet.transports[0].license_plate.should == 'DCU2013'
130
+ end
131
+
132
+ should "store different associations" do
133
+ fleet = TrModels::Fleet.new
134
+ fleet.transports = [
135
+ TrModels::Car.new('license_plate' => 'ABC1223', 'model' => 'Honda Civic', 'year' => 2003),
136
+ TrModels::Bus.new('license_plate' => 'XYZ9090', 'max_passengers' => 51),
137
+ TrModels::Ambulance.new('license_plate' => 'HDD3030', 'icu' => true)
138
+ ]
139
+ fleet.save.should be_true
140
+
141
+ fleet = fleet.reload
142
+ fleet.transports.size.should == 3
143
+ fleet.transports[0].license_plate.should == 'ABC1223'
144
+ fleet.transports[0].model.should == 'Honda Civic'
145
+ fleet.transports[0].year.should == 2003
146
+ fleet.transports[1].license_plate.should == 'XYZ9090'
147
+ fleet.transports[1].max_passengers.should == 51
148
+ fleet.transports[2].license_plate.should == 'HDD3030'
149
+ fleet.transports[2].icu.should == true
150
+ end
151
+ end
152
+
153
+ context "extending the association" do
154
+ should "work using a block passed to many" do
155
+ catalog = Catalog.new
156
+ medias = catalog.medias = [
157
+ Video.new('file' => 'video.mpg', 'length' => 3600, :visible => true),
158
+ Music.new('file' => 'music.mp3', 'bitrate' => '128kbps', :visible => true),
159
+ Image.new('file' => 'image.png', 'width' => 800, 'height' => 600, :visible => false)
160
+ ]
161
+ catalog.save
162
+ catalog.medias.visible.should == [medias[0], medias[1]]
163
+ end
164
+
165
+ should "work using many's :extend option" do
166
+ fleet = TrModels::Fleet.new
167
+ transports = fleet.transports = [
168
+ TrModels::Car.new('license_plate' => 'ABC1223', 'model' => 'Honda Civic', 'year' => 2003, :purchased_on => 2.years.ago.to_date),
169
+ TrModels::Bus.new('license_plate' => 'XYZ9090', 'max_passengers' => 51, :purchased_on => 3.years.ago.to_date),
170
+ TrModels::Ambulance.new('license_plate' => 'HDD3030', 'icu' => true, :purchased_on => 1.year.ago.to_date)
171
+ ]
172
+ fleet.save
173
+ fleet.transports.to_be_replaced.should == [transports[1]]
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,256 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class ManyEmbeddedProxyTest < Test::Unit::TestCase
5
+ def setup
6
+ @comment_class = EDoc do
7
+ key :name, String
8
+ key :body, String
9
+ end
10
+ @post_class = Doc do
11
+ key :title, String
12
+ end
13
+ @post_class.many :comments, :class => @comment_class
14
+
15
+ @pet_class = EDoc do
16
+ key :name, String
17
+ end
18
+ @pet_class.embedded_in :person
19
+ @person_class = EDoc do
20
+ key :name, String
21
+ end
22
+ @person_class.key :child, @person_class
23
+ @person_class.many :pets, :class => @pet_class
24
+
25
+ @owner_class = Doc do
26
+ key :name, String
27
+ end
28
+ @owner_class.many :pets, :class => @pet_class
29
+ end
30
+
31
+ should "default reader to empty array" do
32
+ @post_class.new.comments.should == []
33
+ end
34
+
35
+ should "allow adding to association like it was an array" do
36
+ post = @post_class.new
37
+ post.comments << @comment_class.new
38
+ post.comments.push @comment_class.new
39
+ post.comments.size.should == 2
40
+ end
41
+
42
+ should "be embedded in document on save" do
43
+ frank = @comment_class.new(:name => 'Frank', :body => 'Hi!')
44
+ bill = @comment_class.new(:name => 'Bill', :body => 'Hi!')
45
+ post = @post_class.new
46
+ post.comments << frank
47
+ post.comments << bill
48
+ post.save
49
+
50
+ post.reload
51
+ post.comments.size.should == 2
52
+ post.comments[0].should == frank
53
+ post.comments[0].new?.should == false
54
+ post.comments[1].should == bill
55
+ post.comments[1].new?.should == false
56
+ end
57
+
58
+ should "allow embedding arbitrarily deep" do
59
+ @klass = Doc()
60
+ @klass.key :person, @person_class
61
+
62
+ meg = @person_class.new(:name => 'Meg')
63
+ meg.child = @person_class.new(:name => 'Steve')
64
+ meg.child.child = @person_class.new(:name => 'Linda')
65
+
66
+ doc = @klass.new(:person => meg)
67
+ doc.save
68
+ doc.reload
69
+
70
+ doc.person.name.should == 'Meg'
71
+ doc.person.child.name.should == 'Steve'
72
+ doc.person.child.child.name.should == 'Linda'
73
+ end
74
+
75
+ should "allow assignment of many embedded documents using a hash" do
76
+ person_attributes = {
77
+ 'name' => 'Mr. Pet Lover',
78
+ 'pets' => [
79
+ {'name' => 'Jimmy', 'species' => 'Cocker Spainel'},
80
+ {'name' => 'Sasha', 'species' => 'Siberian Husky'},
81
+ ]
82
+ }
83
+
84
+ owner = @owner_class.new(person_attributes)
85
+ owner.name.should == 'Mr. Pet Lover'
86
+ owner.pets[0].name.should == 'Jimmy'
87
+ owner.pets[0].species.should == 'Cocker Spainel'
88
+ owner.pets[1].name.should == 'Sasha'
89
+ owner.pets[1].species.should == 'Siberian Husky'
90
+
91
+ owner.save.should be_true
92
+ owner.reload
93
+
94
+ owner.name.should == 'Mr. Pet Lover'
95
+ owner.pets[0].name.should == 'Jimmy'
96
+ owner.pets[0].species.should == 'Cocker Spainel'
97
+ owner.pets[1].name.should == 'Sasha'
98
+ owner.pets[1].species.should == 'Siberian Husky'
99
+ end
100
+
101
+ context "embedding many embedded documents" do
102
+ setup do
103
+ @klass = Doc()
104
+ @klass.many :people, :class => @person_class
105
+ end
106
+
107
+ should "persist all embedded documents" do
108
+ meg = @person_class.new(:name => 'Meg', :pets => [
109
+ @pet_class.new(:name => 'Sparky', :species => 'Dog'),
110
+ @pet_class.new(:name => 'Koda', :species => 'Dog')
111
+ ])
112
+
113
+ doc = @klass.new
114
+ doc.people << meg
115
+ doc.save
116
+ doc.reload
117
+
118
+ doc.people.first.name.should == 'Meg'
119
+ doc.people.first.pets.should_not == []
120
+ doc.people.first.pets.first.name.should == 'Sparky'
121
+ doc.people.first.pets.first.species.should == 'Dog'
122
+ doc.people.first.pets[1].name.should == 'Koda'
123
+ doc.people.first.pets[1].species.should == 'Dog'
124
+ end
125
+
126
+ should "create a reference to the root document for all embedded documents before save" do
127
+ doc = @klass.new
128
+ meg = @person_class.new(:name => 'Meg')
129
+ pet = @pet_class.new(:name => 'Sparky', :species => 'Dog')
130
+
131
+ doc.people << meg
132
+ meg.pets << pet
133
+
134
+ doc.people.first._root_document.should == doc
135
+ doc.people.first.pets.first._root_document.should == doc
136
+ end
137
+ should "create a reference to the owning document for all embedded documents before save" do
138
+ doc = @klass.new
139
+ meg = @person_class.new(:name => 'Meg')
140
+ pet = @pet_class.new(:name => 'Sparky', :species => 'Dog')
141
+
142
+ doc.people << meg
143
+ meg.pets << pet
144
+
145
+ doc.people.first._parent_document.should == doc
146
+ doc.people.first.pets.first._parent_document.should == doc.people.first
147
+ end
148
+
149
+ should "create a reference to the root document for all embedded documents" do
150
+ sparky = @pet_class.new(:name => 'Sparky', :species => 'Dog')
151
+ meg = @person_class.new(:name => 'Meg', :pets => [sparky])
152
+ doc = @klass.new
153
+ doc.people << meg
154
+ doc.save
155
+
156
+ doc.reload
157
+ doc.people.first._root_document.should == doc
158
+ doc.people.first.pets.first._root_document.should == doc
159
+ end
160
+ should "create a reference to the owning document for all embedded documents" do
161
+ doc = @klass.new
162
+ meg = @person_class.new(:name => 'Meg')
163
+ pet = @pet_class.new(:name => 'Sparky', :species => 'Dog')
164
+
165
+ doc.people << meg
166
+ meg.pets << pet
167
+ doc.save
168
+
169
+ doc.reload
170
+ doc.people.first._parent_document.should == doc
171
+ doc.people.first.pets.first._parent_document.should == doc.people.first
172
+ end
173
+
174
+ should "create embedded_in relationship for embedded docs" do
175
+ doc = @klass.new
176
+ meg = @person_class.new(:name => 'Meg')
177
+ pet = @pet_class.new(:name => 'Sparky', :species => 'Dog')
178
+
179
+ doc.people << meg
180
+ meg.pets << pet
181
+ doc.save
182
+
183
+ doc.reload
184
+ doc.people.first.pets.first.person.should == doc.people.first
185
+ end
186
+ end
187
+
188
+ should "allow finding by id" do
189
+ sparky = @pet_class.new(:name => 'Sparky', :species => 'Dog')
190
+ meg = @owner_class.create(:name => 'Meg', :pets => [sparky])
191
+
192
+ meg.pets.find(sparky._id).should == sparky # oid
193
+ meg.pets.find(sparky.id.to_s).should == sparky # string
194
+ end
195
+
196
+ context "count" do
197
+ should "default to 0" do
198
+ @owner_class.new.pets.count.should == 0
199
+ end
200
+
201
+ should "return correct count if any are embedded" do
202
+ owner = @owner_class.new(:name => 'Meg')
203
+ owner.pets = [@pet_class.new, @pet_class.new]
204
+ owner.pets.count.should == 2
205
+ owner.save
206
+ owner.reload
207
+ owner.pets.count.should == 2
208
+ end
209
+ end
210
+
211
+ context "extending the association" do
212
+ setup do
213
+ @address_class = EDoc do
214
+ key :address, String
215
+ key :city, String
216
+ key :state, String
217
+ key :zip, Integer
218
+ end
219
+
220
+ @project_class = Doc do
221
+ key :name, String
222
+ end
223
+ end
224
+
225
+ should "work using a block passed to many" do
226
+ @project_class.many :addresses, :class => @address_class do
227
+ def find_all_by_state(state)
228
+ find_all { |a| a.state == state }
229
+ end
230
+ end
231
+
232
+ addr1 = @address_class.new(:address => "Gate-3 Lankershim Blvd.", :city => "Universal City", :state => "CA", :zip => "91608")
233
+ addr2 = @address_class.new(:address => "3000 W. Alameda Ave.", :city => "Burbank", :state => "CA", :zip => "91523")
234
+ addr3 = @address_class.new(:address => "111 Some Ln", :city => "Nashville", :state => "TN", :zip => "37211")
235
+ project = @project_class.create(:name => "Some Project", :addresses => [addr1, addr2, addr3])
236
+
237
+ project.addresses.find_all_by_state("CA").should == [addr1, addr2]
238
+ end
239
+
240
+ should "work using many's :extend option" do
241
+ module FindByCity
242
+ def find_by_city(city)
243
+ find_all { |a| a.city == city }
244
+ end
245
+ end
246
+ @project_class.many :addresses, :class => @address_class, :extend => FindByCity
247
+
248
+ addr1 = @address_class.new(:address => "Gate-3 Lankershim Blvd.", :city => "Universal City", :state => "CA", :zip => "91608")
249
+ addr2 = @address_class.new(:address => "3000 W. Alameda Ave.", :city => "Burbank", :state => "CA", :zip => "91523")
250
+ addr3 = @address_class.new(:address => "111 Some Ln", :city => "Nashville", :state => "TN", :zip => "37211")
251
+ project = @project_class.create(:name => "Some Project", :addresses => [addr1, addr2, addr3])
252
+
253
+ project.addresses.find_by_city('Burbank').should == [addr2]
254
+ end
255
+ end
256
+ end
@@ -0,0 +1,302 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class ManyPolymorphicProxyTest < Test::Unit::TestCase
5
+ def setup
6
+ Room.collection.remove
7
+ Message.collection.remove
8
+ end
9
+
10
+ should "default reader to empty array" do
11
+ Room.new.messages.should == []
12
+ end
13
+
14
+ should "add type key to polymorphic class base" do
15
+ Message.keys.keys.should include('_type')
16
+ end
17
+
18
+ should "allow adding to assiciation like it was an array" do
19
+ room = Room.new
20
+ room.messages << Enter.new
21
+ room.messages.push Exit.new
22
+ room.messages.concat Exit.new
23
+ room.messages.size.should == 3
24
+ end
25
+
26
+ should "be able to replace the association" do
27
+ room = Room.create(:name => 'Lounge')
28
+
29
+ lambda {
30
+ room.messages = [
31
+ Enter.new(:body => 'John entered room', :position => 1),
32
+ Chat.new(:body => 'Heyyyoooo!', :position => 2),
33
+ Exit.new(:body => 'John exited room', :position => 3)
34
+ ]
35
+ }.should change { Message.count }.by(3)
36
+
37
+ room = room.reload
38
+ messages = room.messages.all :order => "position"
39
+ messages.size.should == 3
40
+ messages[0].body.should == 'John entered room'
41
+ messages[1].body.should == 'Heyyyoooo!'
42
+ messages[2].body.should == 'John exited room'
43
+ end
44
+
45
+ should "correctly store type when using <<, push and concat" do
46
+ room = Room.new
47
+ room.messages << Enter.new(:body => 'John entered the room', :position => 1)
48
+ room.messages.push Exit.new(:body => 'John entered the room', :position => 2)
49
+ room.messages.concat Chat.new(:body => 'Holla!' , :position => 3)
50
+
51
+ room = room.reload
52
+ messages = room.messages.all :order => "position"
53
+ messages[0]._type.should == 'Enter'
54
+ messages[1]._type.should == 'Exit'
55
+ messages[2]._type.should == 'Chat'
56
+ end
57
+
58
+ context "build" do
59
+ should "assign foreign key" do
60
+ room = Room.create
61
+ message = room.messages.build
62
+ message.room_id.should == room._id
63
+ end
64
+
65
+ should "assign _type" do
66
+ room = Room.create
67
+ message = room.messages.build
68
+ message._type.should == 'Message'
69
+ end
70
+
71
+ should "allow assigning attributes" do
72
+ room = Room.create
73
+ message = room.messages.build(:body => 'Foo!')
74
+ message.body.should == 'Foo!'
75
+ end
76
+ end
77
+
78
+ context "create" do
79
+ should "assign foreign key" do
80
+ room = Room.create
81
+ message = room.messages.create
82
+ message.room_id.should == room._id
83
+ end
84
+
85
+ should "assign _type" do
86
+ room = Room.create
87
+ message = room.messages.create
88
+ message._type.should == 'Message'
89
+ end
90
+
91
+ should "save record" do
92
+ room = Room.create
93
+ lambda {
94
+ room.messages.create
95
+ }.should change { Message.count }
96
+ end
97
+
98
+ should "allow passing attributes" do
99
+ room = Room.create
100
+ message = room.messages.create(:body => 'Foo!')
101
+ message.body.should == 'Foo!'
102
+ end
103
+ end
104
+
105
+ context "count" do
106
+ should "work scoped to association" do
107
+ room = Room.create
108
+ 3.times { room.messages.create }
109
+
110
+ other_room = Room.create
111
+ 2.times { other_room.messages.create }
112
+
113
+ room.messages.count.should == 3
114
+ other_room.messages.count.should == 2
115
+ end
116
+
117
+ should "work with conditions" do
118
+ room = Room.create
119
+ room.messages.create(:body => 'Foo')
120
+ room.messages.create(:body => 'Other 1')
121
+ room.messages.create(:body => 'Other 2')
122
+
123
+ room.messages.count(:body => 'Foo').should == 1
124
+ end
125
+ end
126
+
127
+ context "Finding scoped to association" do
128
+ setup do
129
+ @lounge = Room.create(:name => 'Lounge')
130
+ @lm1 = Message.create(:body => 'Loungin!', :position => 1)
131
+ @lm2 = Message.create(:body => 'I love loungin!', :position => 2)
132
+ @lounge.messages = [@lm1, @lm2]
133
+ @lounge.save
134
+
135
+ @hall = Room.create(:name => 'Hall')
136
+ @hm1 = Message.create(:body => 'Do not fall in the hall', :position => 1)
137
+ @hm3 = Message.create(:body => 'Loungin!', :position => 3)
138
+ @hm2 = Message.create(:body => 'Hall the king!', :position => 2)
139
+ @hall.messages = [@hm1, @hm2, @hm3]
140
+ @hall.save
141
+ end
142
+
143
+ context "dynamic finders" do
144
+ should "work with single key" do
145
+ @lounge.messages.find_by_position(1).should == @lm1
146
+ @hall.messages.find_by_position(2).should == @hm2
147
+ end
148
+
149
+ should "work with multiple keys" do
150
+ @lounge.messages.find_by_body_and_position('Loungin!', 1).should == @lm1
151
+ @lounge.messages.find_by_body_and_position('Loungin!', 2).should be_nil
152
+ end
153
+
154
+ should "raise error when using !" do
155
+ lambda {
156
+ @lounge.messages.find_by_position!(222)
157
+ }.should raise_error(MongoMapper::DocumentNotFound)
158
+ end
159
+
160
+ context "find_or_create_by" do
161
+ should "not create document if found" do
162
+ lambda {
163
+ message = @lounge.messages.find_or_create_by_body('Loungin!')
164
+ message.room.should == @lounge
165
+ message.should == @lm1
166
+ }.should_not change { Message.count }
167
+ end
168
+
169
+ should "create document if not found" do
170
+ lambda {
171
+ message = @lounge.messages.find_or_create_by_body('Yo dawg!')
172
+ message.room.should == @lounge
173
+ message._type.should == 'Message'
174
+ }.should change { Message.count }.by(1)
175
+ end
176
+ end
177
+ end
178
+
179
+ context "with #all" do
180
+ should "work" do
181
+ @lounge.messages.all(:order => "position").should == [@lm1, @lm2]
182
+ end
183
+
184
+ should "work with conditions" do
185
+ messages = @lounge.messages.all(:body => 'Loungin!', :order => "position")
186
+ messages.should == [@lm1]
187
+ end
188
+
189
+ should "work with order" do
190
+ messages = @lounge.messages.all(:order => 'position desc')
191
+ messages.should == [@lm2, @lm1]
192
+ end
193
+ end
194
+
195
+ context "with #first" do
196
+ should "work" do
197
+ @lounge.messages.first(:order => "position asc").should == @lm1
198
+ end
199
+
200
+ should "work with conditions" do
201
+ message = @lounge.messages.first(:body => 'I love loungin!', :order => "position asc")
202
+ message.should == @lm2
203
+ end
204
+ end
205
+
206
+ context "with #last" do
207
+ should "work" do
208
+ @lounge.messages.last(:order => "position asc").should == @lm2
209
+ end
210
+
211
+ should "work with conditions" do
212
+ message = @lounge.messages.last(:body => 'Loungin!', :order => "position asc")
213
+ message.should == @lm1
214
+ end
215
+ end
216
+
217
+ context "with one id" do
218
+ should "work for id in association" do
219
+ @lounge.messages.find(@lm2._id).should == @lm2
220
+ end
221
+
222
+ should "not work for id not in association" do
223
+ lambda {
224
+ @lounge.messages.find!(@hm2._id)
225
+ }.should raise_error(MongoMapper::DocumentNotFound)
226
+ end
227
+ end
228
+
229
+ context "with query options/criteria" do
230
+ should "work with order on association" do
231
+ @lounge.messages.should == [@lm1, @lm2]
232
+ end
233
+
234
+ should "allow overriding the order provided to the association" do
235
+ @lounge.messages.all(:order => 'position').should == [@lm1, @lm2]
236
+ end
237
+
238
+ should "allow using conditions on association" do
239
+ @hall.latest_messages.should == [@hm3, @hm2]
240
+ end
241
+ end
242
+
243
+ context "with multiple ids" do
244
+ should "work for ids in association" do
245
+ messages = @lounge.messages.find(@lm1._id, @lm2._id)
246
+ messages.should == [@lm1, @lm2]
247
+ end
248
+
249
+ should "not work for ids not in association" do
250
+ assert_raises(MongoMapper::DocumentNotFound) do
251
+ @lounge.messages.find!(@lm1._id, @lm2._id, @hm2._id)
252
+ end
253
+ end
254
+ end
255
+
256
+ context "with #paginate" do
257
+ setup do
258
+ @messages = @hall.messages.paginate(:per_page => 2, :page => 1, :order => 'position asc')
259
+ end
260
+
261
+ should "return total pages" do
262
+ @messages.total_pages.should == 2
263
+ end
264
+
265
+ should "return total entries" do
266
+ @messages.total_entries.should == 3
267
+ end
268
+
269
+ should "return the subject" do
270
+ @messages.should == [@hm1, @hm2]
271
+ end
272
+ end
273
+ end
274
+
275
+ context "extending the association" do
276
+ should "work using a block passed to many" do
277
+ room = Room.new(:name => "Amazing Room")
278
+ messages = room.messages = [
279
+ Enter.new(:body => 'John entered room', :position => 3),
280
+ Chat.new(:body => 'Heyyyoooo!', :position => 4),
281
+ Exit.new(:body => 'John exited room', :position => 5),
282
+ Enter.new(:body => 'Steve entered room', :position => 6),
283
+ Chat.new(:body => 'Anyone there?', :position => 7),
284
+ Exit.new(:body => 'Steve exited room', :position => 8)
285
+ ]
286
+ room.save
287
+ room.messages.older.should == messages[3..5]
288
+ end
289
+
290
+ should "work using many's :extend option" do
291
+
292
+ room = Room.new(:name => "Amazing Room")
293
+ accounts = room.accounts = [
294
+ Bot.new(:last_logged_in => 3.weeks.ago),
295
+ AccountUser.new(:last_logged_in => nil),
296
+ Bot.new(:last_logged_in => 1.week.ago)
297
+ ]
298
+ room.save
299
+ room.accounts.inactive.should == [accounts[1]]
300
+ end
301
+ end
302
+ end