mongo_mapper-unstable 2009.10.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/.gitignore +8 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +50 -0
  4. data/Rakefile +87 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +55 -0
  7. data/lib/mongo_mapper/associations/base.rb +83 -0
  8. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
  9. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +22 -0
  10. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +27 -0
  11. data/lib/mongo_mapper/associations/many_documents_proxy.rb +116 -0
  12. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
  13. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +67 -0
  14. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
  15. data/lib/mongo_mapper/associations/many_proxy.rb +6 -0
  16. data/lib/mongo_mapper/associations/proxy.rb +74 -0
  17. data/lib/mongo_mapper/associations.rb +86 -0
  18. data/lib/mongo_mapper/callbacks.rb +106 -0
  19. data/lib/mongo_mapper/dirty.rb +137 -0
  20. data/lib/mongo_mapper/document.rb +340 -0
  21. data/lib/mongo_mapper/dynamic_finder.rb +35 -0
  22. data/lib/mongo_mapper/embedded_document.rb +355 -0
  23. data/lib/mongo_mapper/finder_options.rb +98 -0
  24. data/lib/mongo_mapper/key.rb +36 -0
  25. data/lib/mongo_mapper/observing.rb +50 -0
  26. data/lib/mongo_mapper/pagination.rb +51 -0
  27. data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
  28. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +27 -0
  29. data/lib/mongo_mapper/save_with_validation.rb +19 -0
  30. data/lib/mongo_mapper/serialization.rb +55 -0
  31. data/lib/mongo_mapper/serializers/json_serializer.rb +92 -0
  32. data/lib/mongo_mapper/support.rb +161 -0
  33. data/lib/mongo_mapper/validations.rb +69 -0
  34. data/lib/mongo_mapper.rb +111 -0
  35. data/mongo_mapper.gemspec +162 -0
  36. data/specs.watchr +32 -0
  37. data/test/NOTE_ON_TESTING +1 -0
  38. data/test/custom_matchers.rb +55 -0
  39. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +55 -0
  40. data/test/functional/associations/test_belongs_to_proxy.rb +49 -0
  41. data/test/functional/associations/test_many_documents_as_proxy.rb +244 -0
  42. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +132 -0
  43. data/test/functional/associations/test_many_embedded_proxy.rb +174 -0
  44. data/test/functional/associations/test_many_polymorphic_proxy.rb +297 -0
  45. data/test/functional/associations/test_many_proxy.rb +331 -0
  46. data/test/functional/test_associations.rb +44 -0
  47. data/test/functional/test_binary.rb +18 -0
  48. data/test/functional/test_callbacks.rb +85 -0
  49. data/test/functional/test_dirty.rb +138 -0
  50. data/test/functional/test_document.rb +1051 -0
  51. data/test/functional/test_embedded_document.rb +97 -0
  52. data/test/functional/test_logger.rb +20 -0
  53. data/test/functional/test_pagination.rb +87 -0
  54. data/test/functional/test_rails_compatibility.rb +30 -0
  55. data/test/functional/test_validations.rb +279 -0
  56. data/test/models.rb +195 -0
  57. data/test/test_helper.rb +30 -0
  58. data/test/unit/serializers/test_json_serializer.rb +189 -0
  59. data/test/unit/test_association_base.rb +144 -0
  60. data/test/unit/test_document.rb +184 -0
  61. data/test/unit/test_dynamic_finder.rb +125 -0
  62. data/test/unit/test_embedded_document.rb +656 -0
  63. data/test/unit/test_finder_options.rb +261 -0
  64. data/test/unit/test_key.rb +172 -0
  65. data/test/unit/test_mongomapper.rb +28 -0
  66. data/test/unit/test_observing.rb +101 -0
  67. data/test/unit/test_pagination.rb +109 -0
  68. data/test/unit/test_rails_compatibility.rb +39 -0
  69. data/test/unit/test_serializations.rb +52 -0
  70. data/test/unit/test_support.rb +291 -0
  71. data/test/unit/test_time_zones.rb +40 -0
  72. data/test/unit/test_validations.rb +503 -0
  73. metadata +210 -0
@@ -0,0 +1,244 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class ManyDocumentsAsProxyTest < Test::Unit::TestCase
5
+ def setup
6
+ Post.collection.clear
7
+ PostComment.collection.clear
8
+ end
9
+
10
+ should "default reader to empty array" do
11
+ Post.new.comments.should == []
12
+ end
13
+
14
+ should "add type and id key to polymorphic class base" do
15
+ PostComment.keys.keys.should include('commentable_type')
16
+ PostComment.keys.keys.should include('commentable_id')
17
+ end
18
+
19
+ should "allow adding to association like it was an array" do
20
+ post = Post.new
21
+ post.comments << PostComment.new(:body => 'foo bar')
22
+ post.comments << PostComment.new(:body => 'baz')
23
+ post.comments.concat PostComment.new(:body => 'baz')
24
+
25
+ post.comments.size.should == 3
26
+ end
27
+
28
+ should "be able to replace the association" do
29
+ post = Post.new
30
+
31
+ lambda {
32
+ post.comments = [
33
+ PostComment.new(:body => 'foo'),
34
+ PostComment.new(:body => 'bar'),
35
+ PostComment.new(:body => 'baz')
36
+ ]
37
+ }.should change { PostComment.count }.by(3)
38
+
39
+ from_db = Post.find(post.id)
40
+ from_db.comments.size.should == 3
41
+ from_db.comments[0].body.should == 'foo'
42
+ from_db.comments[1].body.should == 'bar'
43
+ from_db.comments[2].body.should == 'baz'
44
+ end
45
+
46
+ context "build" do
47
+ should "assign foreign key" do
48
+ post = Post.new
49
+ comment = post.comments.build
50
+ comment.commentable_id.should == post.id
51
+ end
52
+
53
+ should "assign _type" do
54
+ post = Post.new
55
+ comment = post.comments.build
56
+ comment.commentable_type.should == "Post"
57
+ end
58
+
59
+ should "allow assigning attributes" do
60
+ post = Post.new
61
+ comment = post.comments.build(:body => 'foo bar')
62
+ comment.body.should == 'foo bar'
63
+ end
64
+ end
65
+
66
+ context "create" do
67
+ should "assign foreign key" do
68
+ post = Post.new
69
+ comment = post.comments.create
70
+ comment.commentable_id.should == post.id
71
+ end
72
+
73
+ should "assign _type" do
74
+ post = Post.new
75
+ comment = post.comments.create
76
+ comment.commentable_type.should == "Post"
77
+ end
78
+
79
+ should "save record" do
80
+ post = Post.new
81
+ lambda {
82
+ post.comments.create(:body => 'baz')
83
+ }.should change { PostComment.count }
84
+ end
85
+
86
+ should "allow passing attributes" do
87
+ post = Post.create
88
+ comment = post.comments.create(:body => 'foo bar')
89
+ comment.body.should == 'foo bar'
90
+ end
91
+ end
92
+
93
+ context "count" do
94
+ should "work scoped to association" do
95
+ post = Post.create
96
+ 3.times { post.comments.create(:body => 'foo bar') }
97
+
98
+ other_post = Post.create
99
+ 2.times { other_post.comments.create(:body => 'baz') }
100
+
101
+ post.comments.count.should == 3
102
+ other_post.comments.count.should == 2
103
+ end
104
+
105
+ should "work with conditions" do
106
+ post = Post.create
107
+ post.comments.create(:body => 'foo bar')
108
+ post.comments.create(:body => 'baz')
109
+ post.comments.create(:body => 'foo bar')
110
+
111
+ post.comments.count(:body => 'foo bar').should == 2
112
+ end
113
+ end
114
+
115
+ context "Finding scoped to association" do
116
+ setup do
117
+ @post = Post.new
118
+
119
+ @comment1 = PostComment.create(:body => 'comment1', :name => 'John')
120
+ @comment2 = PostComment.create(:body => 'comment2', :name => 'Steve')
121
+ @comment3 = PostComment.create(:body => 'comment3', :name => 'John')
122
+ @post.comments = [@comment1, @comment2]
123
+ @post.save
124
+
125
+ @post2 = Post.create(:body => "post #2")
126
+ @comment4 = PostComment.create(:body => 'comment1', :name => 'Chas')
127
+ @comment5 = PostComment.create(:body => 'comment2', :name => 'Dan')
128
+ @comment6 = PostComment.create(:body => 'comment3', :name => 'Ed')
129
+ @post2.comments = [@comment4, @comment5, @comment6]
130
+ @post2.save
131
+ end
132
+
133
+ context "with :all" do
134
+ should "work" do
135
+ @post.comments.find(:all).should include(@comment1)
136
+ @post.comments.find(:all).should include(@comment2)
137
+ end
138
+
139
+ should "work with conditions" do
140
+ comments = @post.comments.find(:all, :conditions => {:body => 'comment1'})
141
+ comments.should == [@comment1]
142
+ end
143
+
144
+ should "work with order" do
145
+ comments = @post.comments.find(:all, :order => 'body desc')
146
+ comments.should == [@comment2, @comment1]
147
+ end
148
+ end
149
+
150
+ context "with #all" do
151
+ should "work" do
152
+ @post.comments.all.should == [@comment1, @comment2]
153
+ end
154
+
155
+ should "work with conditions" do
156
+ comments = @post.comments.all(:conditions => {:body => 'comment1'})
157
+ comments.should == [@comment1]
158
+ end
159
+
160
+ should "work with order" do
161
+ comments = @post.comments.all(:order => 'body desc')
162
+ comments.should == [@comment2, @comment1]
163
+ end
164
+ end
165
+
166
+ context "with one id" do
167
+ should "work for id in association" do
168
+ @post.comments.find(@comment2.id).should == @comment2
169
+ end
170
+
171
+ should "not work for id not in association" do
172
+ lambda {
173
+ @post.comments.find(@comment5.id)
174
+ }.should raise_error(MongoMapper::DocumentNotFound)
175
+ end
176
+ end
177
+
178
+ context "with multiple ids" do
179
+ should "work for ids in association" do
180
+ posts = @post.comments.find(@comment1.id, @comment2.id)
181
+ posts.should == [@comment1, @comment2]
182
+ end
183
+
184
+ should "not work for ids not in association" do
185
+ lambda {
186
+ @post.comments.find(@comment1.id, @comment2.id, @comment4.id)
187
+ }.should raise_error(MongoMapper::DocumentNotFound)
188
+ end
189
+ end
190
+
191
+ context "dynamic finders" do
192
+ should "work with single key" do
193
+ @post.comments.find_by_body('comment1').should == @comment1
194
+ @post2.comments.find_by_body('comment1').should == @comment4
195
+ end
196
+
197
+ should "work with multiple keys" do
198
+ @post.comments.find_by_body_and_name('comment1', 'John').should == @comment1
199
+ @post.comments.find_by_body_and_name('comment1', 'Frank').should be_nil
200
+ end
201
+
202
+ should "raise error when using !" do
203
+ lambda {
204
+ @post.comments.find_by_body!('asdf')
205
+ }.should raise_error(MongoMapper::DocumentNotFound)
206
+ end
207
+
208
+ context "find_or_create_by" do
209
+ should "not create document if found" do
210
+ lambda {
211
+ comment = @post.comments.find_or_create_by_name('Steve')
212
+ comment.commentable.should == @post
213
+ comment.should == @comment2
214
+ }.should_not change { PostComment.count }
215
+ end
216
+
217
+ should "create document if not found" do
218
+ lambda {
219
+ @post.comments.find_or_create_by_name('Chas')
220
+ }.should change { PostComment.count }.by(1)
221
+ end
222
+ end
223
+ end
224
+
225
+ context "with #paginate" do
226
+ setup do
227
+ @comments = @post2.comments.paginate(:per_page => 2, :page => 1, :order => 'name')
228
+ end
229
+
230
+ should "return total pages" do
231
+ @comments.total_pages.should == 2
232
+ end
233
+
234
+ should "return total entries" do
235
+ @comments.total_entries.should == 3
236
+ end
237
+
238
+ should "return the subject" do
239
+ @comments.should include(@comment4)
240
+ @comments.should include(@comment5)
241
+ end
242
+ end
243
+ end
244
+ end
@@ -0,0 +1,132 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class ManyEmbeddedPolymorphicProxyTest < Test::Unit::TestCase
5
+ def setup
6
+ Catalog.collection.clear
7
+ TrModels::Fleet.collection.clear
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
+ from_db = Catalog.find(catalog.id)
28
+ from_db.medias.size.should == 1
29
+ from_db.medias[0].file.should == "video.mpg"
30
+ end
31
+
32
+ should "store different associations" do
33
+ catalog = Catalog.new
34
+ catalog.medias = [
35
+ Video.new("file" => "video.mpg", "length" => 3600),
36
+ Music.new("file" => "music.mp3", "bitrate" => "128kbps"),
37
+ Image.new("file" => "image.png", "width" => 800, "height" => 600)
38
+ ]
39
+ catalog.save.should be_true
40
+
41
+ from_db = Catalog.find(catalog.id)
42
+ from_db.medias.size.should == 3
43
+ from_db.medias[0].file.should == "video.mpg"
44
+ from_db.medias[0].length.should == 3600
45
+ from_db.medias[1].file.should == "music.mp3"
46
+ from_db.medias[1].bitrate.should == "128kbps"
47
+ from_db.medias[2].file.should == "image.png"
48
+ from_db.medias[2].width.should == 800
49
+ from_db.medias[2].height.should == 600
50
+ end
51
+
52
+ context "With modularized models" do
53
+ should "set associations correctly" do
54
+ fleet_attributes = {
55
+ "name" => "My Fleet",
56
+ "transports" => [
57
+ {"_type" => "TrModels::Ambulance", "license_plate" => "GGG123", "icu" => true},
58
+ {"_type" => "TrModels::Car", "license_plate" => "ABC123", "model" => "VW Golf", "year" => 2001},
59
+ {"_type" => "TrModels::Car", "license_plate" => "DEF123", "model" => "Honda Accord", "year" => 2008},
60
+ ]
61
+ }
62
+
63
+ fleet = TrModels::Fleet.new(fleet_attributes)
64
+ fleet.transports.size.should == 3
65
+ fleet.transports[0].class.should == TrModels::Ambulance
66
+ fleet.transports[0].license_plate.should == "GGG123"
67
+ fleet.transports[0].icu.should be_true
68
+ fleet.transports[1].class.should == TrModels::Car
69
+ fleet.transports[1].license_plate.should == "ABC123"
70
+ fleet.transports[1].model.should == "VW Golf"
71
+ fleet.transports[1].year.should == 2001
72
+ fleet.transports[2].class.should == TrModels::Car
73
+ fleet.transports[2].license_plate.should == "DEF123"
74
+ fleet.transports[2].model.should == "Honda Accord"
75
+ fleet.transports[2].year.should == 2008
76
+ fleet.save.should be_true
77
+
78
+ from_db = TrModels::Fleet.find(fleet.id)
79
+ from_db.transports.size.should == 3
80
+ from_db.transports[0].license_plate.should == "GGG123"
81
+ from_db.transports[0].icu.should be_true
82
+ from_db.transports[1].license_plate.should == "ABC123"
83
+ from_db.transports[1].model.should == "VW Golf"
84
+ from_db.transports[1].year.should == 2001
85
+ from_db.transports[2].license_plate.should == "DEF123"
86
+ from_db.transports[2].model.should == "Honda Accord"
87
+ from_db.transports[2].year.should == 2008
88
+ end
89
+
90
+ should "default reader to empty array" do
91
+ fleet = TrModels::Fleet.new
92
+ fleet.transports.should == []
93
+ end
94
+
95
+ should "allow adding to association like it was an array" do
96
+ fleet = TrModels::Fleet.new
97
+ fleet.transports << TrModels::Car.new
98
+ fleet.transports.push TrModels::Bus.new
99
+ fleet.transports.size.should == 2
100
+ end
101
+
102
+ should "be able to replace the association" do
103
+ fleet = TrModels::Fleet.new
104
+ fleet.transports = [TrModels::Car.new("license_plate" => "DCU2013", "model" => "Honda Civic")]
105
+ fleet.save.should be_true
106
+
107
+ from_db = TrModels::Fleet.find(fleet.id)
108
+ from_db.transports.size.should == 1
109
+ from_db.transports[0].license_plate.should == "DCU2013"
110
+ end
111
+
112
+ should "store different associations" do
113
+ fleet = TrModels::Fleet.new
114
+ fleet.transports = [
115
+ TrModels::Car.new("license_plate" => "ABC1223", "model" => "Honda Civic", "year" => 2003),
116
+ TrModels::Bus.new("license_plate" => "XYZ9090", "max_passengers" => 51),
117
+ TrModels::Ambulance.new("license_plate" => "HDD3030", "icu" => true)
118
+ ]
119
+ fleet.save.should be_true
120
+
121
+ from_db = TrModels::Fleet.find(fleet.id)
122
+ from_db.transports.size.should == 3
123
+ from_db.transports[0].license_plate.should == "ABC1223"
124
+ from_db.transports[0].model.should == "Honda Civic"
125
+ from_db.transports[0].year.should == 2003
126
+ from_db.transports[1].license_plate.should == "XYZ9090"
127
+ from_db.transports[1].max_passengers.should == 51
128
+ from_db.transports[2].license_plate.should == "HDD3030"
129
+ from_db.transports[2].icu.should == true
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,174 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class ManyEmbeddedProxyTest < Test::Unit::TestCase
5
+ def setup
6
+ Project.collection.clear
7
+ RealPerson.collection.clear
8
+ end
9
+
10
+ should "default reader to empty array" do
11
+ Project.new.addresses.should == []
12
+ end
13
+
14
+ should "allow adding to association like it was an array" do
15
+ project = Project.new
16
+ project.addresses << Address.new
17
+ project.addresses.push Address.new
18
+ project.addresses.size.should == 2
19
+ end
20
+
21
+ should "allow finding :all embedded documents" do
22
+ project = Project.new
23
+ project.addresses << Address.new
24
+ project.addresses << Address.new
25
+ project.save
26
+ end
27
+
28
+ should "be embedded in document on save" do
29
+ sb = Address.new(:city => 'South Bend', :state => 'IN')
30
+ chi = Address.new(:city => 'Chicago', :state => 'IL')
31
+ project = Project.new
32
+ project.addresses << sb
33
+ project.addresses << chi
34
+ project.save
35
+
36
+ from_db = Project.find(project.id)
37
+ from_db.addresses.size.should == 2
38
+ from_db.addresses[0].should == sb
39
+ from_db.addresses[1].should == chi
40
+ end
41
+
42
+ should "allow embedding arbitrarily deep" do
43
+ @document = Class.new do
44
+ include MongoMapper::Document
45
+ set_collection_name 'test'
46
+ key :person, Person
47
+ end
48
+ @document.collection.clear
49
+
50
+ meg = Person.new(:name => "Meg")
51
+ meg.child = Person.new(:name => "Steve")
52
+ meg.child.child = Person.new(:name => "Linda")
53
+
54
+ doc = @document.new(:person => meg)
55
+ doc.save
56
+
57
+ from_db = @document.find(doc.id)
58
+ from_db.person.name.should == 'Meg'
59
+ from_db.person.child.name.should == 'Steve'
60
+ from_db.person.child.child.name.should == 'Linda'
61
+ end
62
+
63
+ should "allow assignment of 'many' embedded documents using a hash" do
64
+ person_attributes = {
65
+ "name" => "Mr. Pet Lover",
66
+ "pets" => [
67
+ {"name" => "Jimmy", "species" => "Cocker Spainel"},
68
+ {"name" => "Sasha", "species" => "Siberian Husky"},
69
+ ]
70
+ }
71
+
72
+ pet_lover = RealPerson.new(person_attributes)
73
+ pet_lover.name.should == "Mr. Pet Lover"
74
+ pet_lover.pets[0].name.should == "Jimmy"
75
+ pet_lover.pets[0].species.should == "Cocker Spainel"
76
+ pet_lover.pets[1].name.should == "Sasha"
77
+ pet_lover.pets[1].species.should == "Siberian Husky"
78
+ pet_lover.save.should be_true
79
+
80
+ from_db = RealPerson.find(pet_lover.id)
81
+ from_db.name.should == "Mr. Pet Lover"
82
+ from_db.pets[0].name.should == "Jimmy"
83
+ from_db.pets[0].species.should == "Cocker Spainel"
84
+ from_db.pets[1].name.should == "Sasha"
85
+ from_db.pets[1].species.should == "Siberian Husky"
86
+ end
87
+
88
+ context "embedding many embedded documents" do
89
+ setup do
90
+ @document = Class.new do
91
+ include MongoMapper::Document
92
+ set_collection_name 'test'
93
+ many :people
94
+ end
95
+ @document.collection.clear
96
+ end
97
+
98
+ should "persist all embedded documents" do
99
+ meg = Person.new(:name => "Meg")
100
+ sparky = Pet.new(:name => "Sparky", :species => "Dog")
101
+ koda = Pet.new(:name => "Koda", :species => "Dog")
102
+
103
+ doc = @document.new
104
+
105
+ meg.pets << sparky
106
+ meg.pets << koda
107
+
108
+ doc.people << meg
109
+ doc.save
110
+
111
+ from_db = @document.find(doc.id)
112
+ from_db.people.first.name.should == "Meg"
113
+ from_db.people.first.pets.should_not == []
114
+ from_db.people.first.pets.first.name.should == "Sparky"
115
+ from_db.people.first.pets.first.species.should == "Dog"
116
+ from_db.people.first.pets[1].name.should == "Koda"
117
+ from_db.people.first.pets[1].species.should == "Dog"
118
+ end
119
+
120
+ should "create a reference to the root document for all embedded documents before save" do
121
+ meg = Person.new(:name => "Meg")
122
+ sparky = Pet.new(:name => "Sparky", :species => "Dog")
123
+
124
+ doc = @document.new
125
+
126
+ doc.people << meg
127
+ meg.pets << sparky
128
+
129
+ doc.people.first._root_document.should == doc
130
+ doc.people.first.pets.first._root_document.should == doc
131
+ end
132
+
133
+ should "create properly-named reference to parent document when building off association proxy" do
134
+ person = RealPerson.new
135
+ pet = person.pets.build
136
+ person.should == pet.real_person
137
+ end
138
+
139
+
140
+ should "create a reference to the root document for all embedded documents" do
141
+ meg = Person.new(:name => "Meg")
142
+ sparky = Pet.new(:name => "Sparky", :species => "Dog")
143
+
144
+ doc = @document.new
145
+
146
+ meg.pets << sparky
147
+
148
+ doc.people << meg
149
+ doc.save
150
+
151
+ from_db = @document.find(doc.id)
152
+ from_db.people.first._root_document.should == doc
153
+ from_db.people.first.pets.first._root_document.should == doc
154
+ end
155
+ end
156
+
157
+ should "allow retrieval via find(:all)" do
158
+ meg = Person.new(:name => "Meg")
159
+ sparky = Pet.new(:name => "Sparky", :species => "Dog")
160
+
161
+ meg.pets << sparky
162
+
163
+ meg.pets.find(:all).should include(sparky)
164
+ end
165
+
166
+ should "allow retrieval via find(id)" do
167
+ meg = Person.new(:name => "Meg")
168
+ sparky = Pet.new(:name => "Sparky", :species => "Dog")
169
+
170
+ meg.pets << sparky
171
+
172
+ meg.pets.find(sparky.id).should == sparky
173
+ end
174
+ end