jmonteiro-mongo_mapper 0.1.0

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