mrkurt-mongo_mapper 0.6.8

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 (77) 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/lib/mongo_mapper.rb +139 -0
  8. data/lib/mongo_mapper/associations.rb +72 -0
  9. data/lib/mongo_mapper/associations/base.rb +113 -0
  10. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +26 -0
  11. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +21 -0
  12. data/lib/mongo_mapper/associations/collection.rb +19 -0
  13. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +26 -0
  14. data/lib/mongo_mapper/associations/many_documents_proxy.rb +115 -0
  15. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +31 -0
  16. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +54 -0
  17. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
  18. data/lib/mongo_mapper/associations/one_proxy.rb +61 -0
  19. data/lib/mongo_mapper/associations/proxy.rb +111 -0
  20. data/lib/mongo_mapper/callbacks.rb +61 -0
  21. data/lib/mongo_mapper/dirty.rb +117 -0
  22. data/lib/mongo_mapper/document.rb +496 -0
  23. data/lib/mongo_mapper/dynamic_finder.rb +74 -0
  24. data/lib/mongo_mapper/embedded_document.rb +380 -0
  25. data/lib/mongo_mapper/finder_options.rb +145 -0
  26. data/lib/mongo_mapper/key.rb +36 -0
  27. data/lib/mongo_mapper/mongo_mapper.rb +125 -0
  28. data/lib/mongo_mapper/pagination.rb +66 -0
  29. data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
  30. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +28 -0
  31. data/lib/mongo_mapper/serialization.rb +54 -0
  32. data/lib/mongo_mapper/serializers/json_serializer.rb +48 -0
  33. data/lib/mongo_mapper/support.rb +192 -0
  34. data/lib/mongo_mapper/validations.rb +39 -0
  35. data/mongo_mapper.gemspec +173 -0
  36. data/specs.watchr +30 -0
  37. data/test/NOTE_ON_TESTING +1 -0
  38. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +55 -0
  39. data/test/functional/associations/test_belongs_to_proxy.rb +91 -0
  40. data/test/functional/associations/test_many_documents_as_proxy.rb +246 -0
  41. data/test/functional/associations/test_many_documents_proxy.rb +477 -0
  42. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +156 -0
  43. data/test/functional/associations/test_many_embedded_proxy.rb +192 -0
  44. data/test/functional/associations/test_many_polymorphic_proxy.rb +339 -0
  45. data/test/functional/associations/test_one_proxy.rb +131 -0
  46. data/test/functional/test_associations.rb +44 -0
  47. data/test/functional/test_binary.rb +33 -0
  48. data/test/functional/test_callbacks.rb +85 -0
  49. data/test/functional/test_dirty.rb +159 -0
  50. data/test/functional/test_document.rb +1198 -0
  51. data/test/functional/test_embedded_document.rb +135 -0
  52. data/test/functional/test_logger.rb +20 -0
  53. data/test/functional/test_modifiers.rb +242 -0
  54. data/test/functional/test_pagination.rb +95 -0
  55. data/test/functional/test_rails_compatibility.rb +25 -0
  56. data/test/functional/test_string_id_compatibility.rb +72 -0
  57. data/test/functional/test_validations.rb +361 -0
  58. data/test/models.rb +271 -0
  59. data/test/support/custom_matchers.rb +55 -0
  60. data/test/support/timing.rb +16 -0
  61. data/test/test_helper.rb +27 -0
  62. data/test/unit/associations/test_base.rb +182 -0
  63. data/test/unit/associations/test_proxy.rb +91 -0
  64. data/test/unit/serializers/test_json_serializer.rb +189 -0
  65. data/test/unit/test_document.rb +236 -0
  66. data/test/unit/test_dynamic_finder.rb +125 -0
  67. data/test/unit/test_embedded_document.rb +709 -0
  68. data/test/unit/test_finder_options.rb +325 -0
  69. data/test/unit/test_key.rb +172 -0
  70. data/test/unit/test_mongo_mapper.rb +65 -0
  71. data/test/unit/test_pagination.rb +119 -0
  72. data/test/unit/test_rails_compatibility.rb +52 -0
  73. data/test/unit/test_serializations.rb +52 -0
  74. data/test/unit/test_support.rb +346 -0
  75. data/test/unit/test_time_zones.rb +40 -0
  76. data/test/unit/test_validations.rb +503 -0
  77. metadata +239 -0
data/specs.watchr ADDED
@@ -0,0 +1,30 @@
1
+ def run(cmd)
2
+ puts(cmd)
3
+ system(cmd)
4
+ end
5
+
6
+ def run_test_file(file)
7
+ run %Q(ruby -I"lib:test" -rubygems #{file})
8
+ end
9
+
10
+ def run_all_tests
11
+ run "rake test"
12
+ end
13
+
14
+ def related_test_files(path)
15
+ Dir['test/**/*.rb'].select { |file| file =~ /test_#{File.basename(path)}/ }
16
+ end
17
+
18
+ watch('test/test_helper\.rb') { system('clear'); run_all_tests }
19
+ watch('test/.*/test_.*\.rb') { |m| system('clear'); run_test_file(m[0]) }
20
+ watch('lib/.*') { |m| related_test_files(m[0]).each { |file| system('clear'); run_test_file(file) } }
21
+
22
+ # Ctrl-\
23
+ Signal.trap('QUIT') do
24
+ puts " --- Running all tests ---\n\n"
25
+ run_all_tests
26
+ end
27
+
28
+ # Ctrl-C
29
+ Signal.trap('INT') { abort("\n") }
30
+
@@ -0,0 +1 @@
1
+ I am doing my best to keep unit and functional tests separate. As I see them, functional tests hit the database and should never care about internals. Unit tests do not hit the database.
@@ -0,0 +1,55 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class BelongsToPolymorphicProxyTest < Test::Unit::TestCase
5
+ def setup
6
+ Status.collection.remove
7
+ Project.collection.remove
8
+ end
9
+
10
+ should "default to nil" do
11
+ status = Status.new
12
+ status.target.nil?.should be_true
13
+ status.target.inspect.should == "nil"
14
+ end
15
+
16
+ should "be able to replace the association" do
17
+ status = Status.new(:name => 'Foo!')
18
+ project = Project.new(:name => "mongomapper")
19
+ status.target = project
20
+ status.save.should be_true
21
+
22
+ status = status.reload
23
+ status.target.nil?.should be_false
24
+ status.target_id.should == project._id
25
+ status.target_type.should == "Project"
26
+ status.target.name.should == "mongomapper"
27
+ end
28
+
29
+ should "unset the association" do
30
+ status = Status.new(:name => 'Foo!')
31
+ project = Project.new(:name => "mongomapper")
32
+ status.target = project
33
+ status.save.should be_true
34
+
35
+ status = status.reload
36
+ status.target = nil
37
+ status.target_type.nil?.should be_true
38
+ status.target_id.nil?.should be_true
39
+ status.target.nil?.should be_true
40
+ end
41
+
42
+ context "association id set but document not found" do
43
+ setup do
44
+ @status = Status.new(:name => 'Foo!')
45
+ project = Project.new(:name => "mongomapper")
46
+ @status.target = project
47
+ @status.save.should be_true
48
+ project.destroy
49
+ end
50
+
51
+ should "return nil instead of raising error" do
52
+ @status.target.nil?.should be_true
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,91 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class BelongsToProxyTest < Test::Unit::TestCase
5
+ def setup
6
+ @post_class = Class.new do
7
+ include MongoMapper::Document
8
+ end
9
+
10
+ @comment_class = Class.new do
11
+ include MongoMapper::Document
12
+ key :post_id, String
13
+ end
14
+ @comment_class.belongs_to :post, :class => @post_class
15
+
16
+ @post_class.collection.remove
17
+ @comment_class.collection.remove
18
+ end
19
+
20
+ should "default to nil" do
21
+ @comment_class.new.post.nil?.should be_true
22
+ end
23
+
24
+ should "be able to replace the association" do
25
+ post = @post_class.new(:name => 'mongomapper')
26
+ comment = @comment_class.new(:name => 'Foo!', :post => post)
27
+ comment.save.should be_true
28
+
29
+ comment = comment.reload
30
+ comment.post.should == post
31
+ comment.post.nil?.should be_false
32
+ end
33
+
34
+ should "unset the association" do
35
+ post = @post_class.new(:name => 'mongomapper')
36
+ comment = @comment_class.new(:name => 'Foo!', :post => post)
37
+ comment.save.should be_true
38
+
39
+ comment = comment.reload
40
+ comment.post = nil
41
+ comment.post.nil?.should be_true
42
+ end
43
+
44
+ should "return nil if id set but document not found" do
45
+ id = Mongo::ObjectID.new
46
+ @comment_class.new(:name => 'Foo', :post_id => id).post.nil?.should be_true
47
+ end
48
+
49
+ context ":dependent" do
50
+ setup do
51
+ # FIXME: make use of already defined models
52
+ class ::Property
53
+ include MongoMapper::Document
54
+ end
55
+ Property.collection.remove
56
+
57
+ class ::Thing
58
+ include MongoMapper::Document
59
+ key :name, String
60
+ end
61
+ Thing.collection.remove
62
+ end
63
+
64
+ teardown do
65
+ Object.send :remove_const, 'Property' if defined?(::Property)
66
+ Object.send :remove_const, 'Thing' if defined?(::Thing)
67
+ end
68
+
69
+ context "=> destroy" do
70
+ setup do
71
+ Property.key :thing_id, ObjectId
72
+ Property.belongs_to :thing, :dependent => :destroy
73
+ Thing.has_many :properties
74
+
75
+ @thing = Thing.create(:name => "Tree")
76
+ @property1 = Property.create
77
+ @property2 = Property.create
78
+ @property3 = Property.create
79
+ @thing.properties << @property1
80
+ @thing.properties << @property2
81
+ @thing.properties << @property3
82
+ end
83
+
84
+ should "not execute on a belongs_to association" do
85
+ Thing.count.should == 1
86
+ @property1.destroy
87
+ Thing.count.should == 1
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,246 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class ManyDocumentsAsProxyTest < Test::Unit::TestCase
5
+ def setup
6
+ Post.collection.remove
7
+ PostComment.collection.remove
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
+ post = post.reload
40
+ post.comments.size.should == 3
41
+ bodies = post.comments.collect(&:body)
42
+ bodies.should include('foo')
43
+ bodies.should include('bar')
44
+ bodies.should include('baz')
45
+ end
46
+
47
+ context "build" do
48
+ should "assign foreign key" do
49
+ post = Post.new
50
+ comment = post.comments.build
51
+ comment.commentable_id.should == post._id
52
+ end
53
+
54
+ should "assign _type" do
55
+ post = Post.new
56
+ comment = post.comments.build
57
+ comment.commentable_type.should == "Post"
58
+ end
59
+
60
+ should "allow assigning attributes" do
61
+ post = Post.new
62
+ comment = post.comments.build(:body => 'foo bar')
63
+ comment.body.should == 'foo bar'
64
+ end
65
+ end
66
+
67
+ context "create" do
68
+ should "assign foreign key" do
69
+ post = Post.new
70
+ comment = post.comments.create
71
+ comment.commentable_id.should == post._id
72
+ end
73
+
74
+ should "assign _type" do
75
+ post = Post.new
76
+ comment = post.comments.create
77
+ comment.commentable_type.should == "Post"
78
+ end
79
+
80
+ should "save record" do
81
+ post = Post.new
82
+ lambda {
83
+ post.comments.create(:body => 'baz')
84
+ }.should change { PostComment.count }
85
+ end
86
+
87
+ should "allow passing attributes" do
88
+ post = Post.create
89
+ comment = post.comments.create(:body => 'foo bar')
90
+ comment.body.should == 'foo bar'
91
+ end
92
+ end
93
+
94
+ context "count" do
95
+ should "work scoped to association" do
96
+ post = Post.create
97
+ 3.times { post.comments.create(:body => 'foo bar') }
98
+
99
+ other_post = Post.create
100
+ 2.times { other_post.comments.create(:body => 'baz') }
101
+
102
+ post.comments.count.should == 3
103
+ other_post.comments.count.should == 2
104
+ end
105
+
106
+ should "work with conditions" do
107
+ post = Post.create
108
+ post.comments.create(:body => 'foo bar')
109
+ post.comments.create(:body => 'baz')
110
+ post.comments.create(:body => 'foo bar')
111
+
112
+ post.comments.count(:body => 'foo bar').should == 2
113
+ end
114
+ end
115
+
116
+ context "Finding scoped to association" do
117
+ setup do
118
+ @post = Post.new
119
+
120
+ @comment1 = PostComment.create(:body => 'comment1', :name => 'John')
121
+ @comment2 = PostComment.create(:body => 'comment2', :name => 'Steve')
122
+ @comment3 = PostComment.create(:body => 'comment3', :name => 'John')
123
+ @post.comments = [@comment1, @comment2]
124
+ @post.save
125
+
126
+ @post2 = Post.create(:body => "post #2")
127
+ @comment4 = PostComment.create(:body => 'comment1', :name => 'Chas')
128
+ @comment5 = PostComment.create(:body => 'comment2', :name => 'Dan')
129
+ @comment6 = PostComment.create(:body => 'comment3', :name => 'Ed')
130
+ @post2.comments = [@comment4, @comment5, @comment6]
131
+ @post2.save
132
+ end
133
+
134
+ context "with :all" do
135
+ should "work" do
136
+ @post.comments.find(:all).should include(@comment1)
137
+ @post.comments.find(:all).should include(@comment2)
138
+ end
139
+
140
+ should "work with conditions" do
141
+ comments = @post.comments.find(:all, :body => 'comment1')
142
+ comments.should == [@comment1]
143
+ end
144
+
145
+ should "work with order" do
146
+ comments = @post.comments.find(:all, :order => 'body desc')
147
+ comments.should == [@comment2, @comment1]
148
+ end
149
+ end
150
+
151
+ context "with #all" do
152
+ should "work" do
153
+ @post.comments.all.should include(@comment1)
154
+ @post.comments.all.should include(@comment2)
155
+ end
156
+
157
+ should "work with conditions" do
158
+ comments = @post.comments.all(:body => 'comment1')
159
+ comments.should == [@comment1]
160
+ end
161
+
162
+ should "work with order" do
163
+ comments = @post.comments.all(:order => 'body desc')
164
+ comments.should == [@comment2, @comment1]
165
+ end
166
+ end
167
+
168
+ context "with one id" do
169
+ should "work for id in association" do
170
+ @post.comments.find(@comment2._id).should == @comment2
171
+ end
172
+
173
+ should "not work for id not in association" do
174
+ lambda {
175
+ @post.comments.find!(@comment5._id)
176
+ }.should raise_error(MongoMapper::DocumentNotFound)
177
+ end
178
+ end
179
+
180
+ context "with multiple ids" do
181
+ should "work for ids in association" do
182
+ posts = @post.comments.find!(@comment1._id, @comment2._id)
183
+ posts.should == [@comment1, @comment2]
184
+ end
185
+
186
+ should "not work for ids not in association" do
187
+ lambda {
188
+ @post.comments.find!(@comment1._id, @comment2._id, @comment4._id)
189
+ }.should raise_error(MongoMapper::DocumentNotFound)
190
+ end
191
+ end
192
+
193
+ context "dynamic finders" do
194
+ should "work with single key" do
195
+ @post.comments.find_by_body('comment1').should == @comment1
196
+ @post2.comments.find_by_body('comment1').should == @comment4
197
+ end
198
+
199
+ should "work with multiple keys" do
200
+ @post.comments.find_by_body_and_name('comment1', 'John').should == @comment1
201
+ @post.comments.find_by_body_and_name('comment1', 'Frank').should be_nil
202
+ end
203
+
204
+ should "raise error when using !" do
205
+ lambda {
206
+ @post.comments.find_by_body!('asdf')
207
+ }.should raise_error(MongoMapper::DocumentNotFound)
208
+ end
209
+
210
+ context "find_or_create_by" do
211
+ should "not create document if found" do
212
+ lambda {
213
+ comment = @post.comments.find_or_create_by_name('Steve')
214
+ comment.commentable.should == @post
215
+ comment.should == @comment2
216
+ }.should_not change { PostComment.count }
217
+ end
218
+
219
+ should "create document if not found" do
220
+ lambda {
221
+ @post.comments.find_or_create_by_name('Chas')
222
+ }.should change { PostComment.count }.by(1)
223
+ end
224
+ end
225
+ end
226
+
227
+ context "with #paginate" do
228
+ setup do
229
+ @comments = @post2.comments.paginate(:per_page => 2, :page => 1, :order => 'name')
230
+ end
231
+
232
+ should "return total pages" do
233
+ @comments.total_pages.should == 2
234
+ end
235
+
236
+ should "return total entries" do
237
+ @comments.total_entries.should == 3
238
+ end
239
+
240
+ should "return the subject" do
241
+ @comments.should include(@comment4)
242
+ @comments.should include(@comment5)
243
+ end
244
+ end
245
+ end
246
+ end
@@ -0,0 +1,477 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class ManyDocumentsProxyTest < Test::Unit::TestCase
5
+ def setup
6
+ Project.collection.remove
7
+ Status.collection.remove
8
+ end
9
+
10
+ should "default reader to empty array" do
11
+ project = Project.new
12
+ project.statuses.should == []
13
+ end
14
+
15
+ should "allow adding to association like it was an array" do
16
+ project = Project.new
17
+ project.statuses << Status.new(:name => 'Foo1!')
18
+ project.statuses.push Status.new(:name => 'Foo2!')
19
+ project.statuses.concat Status.new(:name => 'Foo3!')
20
+ project.statuses.size.should == 3
21
+ end
22
+
23
+ should "be able to replace the association" do
24
+ project = Project.new
25
+ project.statuses = [Status.new("name" => "ready")]
26
+ project.save.should be_true
27
+
28
+ project = project.reload
29
+ project.statuses.size.should == 1
30
+ project.statuses[0].name.should == "ready"
31
+ end
32
+
33
+ should "correctly assign foreign key when using <<, push and concat" do
34
+ project = Project.new
35
+ project.statuses << Status.new(:name => '<<')
36
+ project.statuses.push Status.new(:name => 'push')
37
+ project.statuses.concat Status.new(:name => 'concat')
38
+
39
+ project = project.reload
40
+ project.statuses[0].project_id.should == project.id
41
+ project.statuses[1].project_id.should == project.id
42
+ project.statuses[2].project_id.should == project.id
43
+ end
44
+
45
+ context "build" do
46
+ should "assign foreign key" do
47
+ project = Project.create
48
+ status = project.statuses.build
49
+ status.project_id.should == project.id
50
+ end
51
+
52
+ should "allow assigning attributes" do
53
+ project = Project.create
54
+ status = project.statuses.build(:name => 'Foo')
55
+ status.name.should == 'Foo'
56
+ end
57
+ end
58
+
59
+ context "create" do
60
+ should "assign foreign key" do
61
+ project = Project.create
62
+ status = project.statuses.create(:name => 'Foo!')
63
+ status.project_id.should == project.id
64
+ end
65
+
66
+ should "save record" do
67
+ project = Project.create
68
+ lambda {
69
+ project.statuses.create(:name => 'Foo!')
70
+ }.should change { Status.count }
71
+ end
72
+
73
+ should "allow passing attributes" do
74
+ project = Project.create
75
+ status = project.statuses.create(:name => 'Foo!')
76
+ status.name.should == 'Foo!'
77
+ end
78
+ end
79
+
80
+ context "create!" do
81
+ should "assign foreign key" do
82
+ project = Project.create
83
+ status = project.statuses.create!(:name => 'Foo!')
84
+ status.project_id.should == project.id
85
+ end
86
+
87
+ should "save record" do
88
+ project = Project.create
89
+ lambda {
90
+ project.statuses.create!(:name => 'Foo!')
91
+ }.should change { Status.count }
92
+ end
93
+
94
+ should "allow passing attributes" do
95
+ project = Project.create
96
+ status = project.statuses.create!(:name => 'Foo!')
97
+ status.name.should == 'Foo!'
98
+ end
99
+
100
+ should "raise exception if not valid" do
101
+ project = Project.create
102
+ lambda {
103
+ project.statuses.create!(:name => nil)
104
+ }.should raise_error(MongoMapper::DocumentNotValid)
105
+ end
106
+ end
107
+
108
+ context "count" do
109
+ should "work scoped to association" do
110
+ project = Project.create
111
+ 3.times { project.statuses.create(:name => 'Foo!') }
112
+
113
+ other_project = Project.create
114
+ 2.times { other_project.statuses.create(:name => 'Foo!') }
115
+
116
+ project.statuses.count.should == 3
117
+ other_project.statuses.count.should == 2
118
+ end
119
+
120
+ should "work with conditions" do
121
+ project = Project.create
122
+ project.statuses.create(:name => 'Foo')
123
+ project.statuses.create(:name => 'Other 1')
124
+ project.statuses.create(:name => 'Other 2')
125
+
126
+ project.statuses.count(:name => 'Foo').should == 1
127
+ end
128
+ end
129
+
130
+ context "Unassociating documents" do
131
+ setup do
132
+ @project = Project.create
133
+ @project.statuses << Status.create(:name => '1')
134
+ @project.statuses << Status.create(:name => '2')
135
+
136
+ @project2 = Project.create
137
+ @project2.statuses << Status.create(:name => '1')
138
+ @project2.statuses << Status.create(:name => '2')
139
+ end
140
+
141
+ should "work with destroy all" do
142
+ @project.statuses.count.should == 2
143
+ @project.statuses.destroy_all
144
+ @project.statuses.count.should == 0
145
+
146
+ @project2.statuses.count.should == 2
147
+ Status.count.should == 2
148
+ end
149
+
150
+ should "work with destroy all and conditions" do
151
+ @project.statuses.count.should == 2
152
+ @project.statuses.destroy_all(:name => '1')
153
+ @project.statuses.count.should == 1
154
+
155
+ @project2.statuses.count.should == 2
156
+ Status.count.should == 3
157
+ end
158
+
159
+ should "work with delete all" do
160
+ @project.statuses.count.should == 2
161
+ @project.statuses.delete_all
162
+ @project.statuses.count.should == 0
163
+
164
+ @project2.statuses.count.should == 2
165
+ Status.count.should == 2
166
+ end
167
+
168
+ should "work with delete all and conditions" do
169
+ @project.statuses.count.should == 2
170
+ @project.statuses.delete_all(:name => '1')
171
+ @project.statuses.count.should == 1
172
+
173
+ @project2.statuses.count.should == 2
174
+ Status.count.should == 3
175
+ end
176
+
177
+ should "work with nullify" do
178
+ @project.statuses.count.should == 2
179
+ @project.statuses.nullify
180
+ @project.statuses.count.should == 0
181
+
182
+ @project2.statuses.count.should == 2
183
+ Status.count.should == 4
184
+ Status.count(:name => '1').should == 2
185
+ Status.count(:name => '2').should == 2
186
+ end
187
+ end
188
+
189
+ context "Finding scoped to association" do
190
+ setup do
191
+ @project1 = Project.new(:name => 'Project 1')
192
+ @brand_new = Status.create(:name => 'New', :position => 1 )
193
+ @complete = Status.create(:name => 'Complete', :position => 2)
194
+ @project1.statuses = [@brand_new, @complete]
195
+ @project1.save
196
+
197
+ @project2 = Project.create(:name => 'Project 2')
198
+ @in_progress = Status.create(:name => 'In Progress')
199
+ @archived = Status.create(:name => 'Archived')
200
+ @another_complete = Status.create(:name => 'Complete')
201
+ @project2.statuses = [@in_progress, @archived, @another_complete]
202
+ @project2.save
203
+ end
204
+
205
+ context "dynamic finders" do
206
+ should "work with single key" do
207
+ @project1.statuses.find_by_name('New').should == @brand_new
208
+ @project1.statuses.find_by_name!('New').should == @brand_new
209
+ @project2.statuses.find_by_name('In Progress').should == @in_progress
210
+ @project2.statuses.find_by_name!('In Progress').should == @in_progress
211
+ end
212
+
213
+ should "work with multiple keys" do
214
+ @project1.statuses.find_by_name_and_position('New', 1).should == @brand_new
215
+ @project1.statuses.find_by_name_and_position!('New', 1).should == @brand_new
216
+ @project1.statuses.find_by_name_and_position('New', 2).should be_nil
217
+ end
218
+
219
+ should "raise error when using !" do
220
+ lambda {
221
+ @project1.statuses.find_by_name!('Fake')
222
+ }.should raise_error(MongoMapper::DocumentNotFound)
223
+ end
224
+
225
+ context "find_or_create_by" do
226
+ should "not create document if found" do
227
+ lambda {
228
+ status = @project1.statuses.find_or_create_by_name('New')
229
+ status.project.should == @project1
230
+ status.should == @brand_new
231
+ }.should_not change { Status.count }
232
+ end
233
+
234
+ should "create document if not found" do
235
+ lambda {
236
+ status = @project1.statuses.find_or_create_by_name('Delivered')
237
+ status.project.should == @project1
238
+ }.should change { Status.count }.by(1)
239
+ end
240
+ end
241
+ end
242
+
243
+ context "with :all" do
244
+ should "work" do
245
+ @project1.statuses.find(:all, :order => "position asc").should == [@brand_new, @complete]
246
+ end
247
+
248
+ should "work with conditions" do
249
+ statuses = @project1.statuses.find(:all, :name => 'Complete')
250
+ statuses.should == [@complete]
251
+ end
252
+
253
+ should "work with order" do
254
+ statuses = @project1.statuses.find(:all, :order => 'name asc')
255
+ statuses.should == [@complete, @brand_new]
256
+ end
257
+ end
258
+
259
+ context "with #all" do
260
+ should "work" do
261
+ @project1.statuses.all(:order => "position asc").should == [@brand_new, @complete]
262
+ end
263
+
264
+ should "work with conditions" do
265
+ statuses = @project1.statuses.all(:name => 'Complete')
266
+ statuses.should == [@complete]
267
+ end
268
+
269
+ should "work with order" do
270
+ statuses = @project1.statuses.all(:order => 'name asc')
271
+ statuses.should == [@complete, @brand_new]
272
+ end
273
+ end
274
+
275
+ context "with :first" do
276
+ should "work" do
277
+ @project1.statuses.find(:first, :order => 'name').should == @complete
278
+ end
279
+
280
+ should "work with conditions" do
281
+ status = @project1.statuses.find(:first, :name => 'Complete')
282
+ status.should == @complete
283
+ end
284
+ end
285
+
286
+ context "with #first" do
287
+ should "work" do
288
+ @project1.statuses.first(:order => 'name').should == @complete
289
+ end
290
+
291
+ should "work with conditions" do
292
+ status = @project1.statuses.first(:name => 'Complete')
293
+ status.should == @complete
294
+ end
295
+ end
296
+
297
+ context "with :last" do
298
+ should "work" do
299
+ @project1.statuses.find(:last, :order => "position asc").should == @complete
300
+ end
301
+
302
+ should "work with conditions" do
303
+ status = @project1.statuses.find(:last, :order => 'position', :name => 'New')
304
+ status.should == @brand_new
305
+ end
306
+ end
307
+
308
+ context "with #last" do
309
+ should "work" do
310
+ @project1.statuses.last(:order => "position asc").should == @complete
311
+ end
312
+
313
+ should "work with conditions" do
314
+ status = @project1.statuses.last(:order => 'position', :name => 'New')
315
+ status.should == @brand_new
316
+ end
317
+ end
318
+
319
+ context "with one id" do
320
+ should "work for id in association" do
321
+ @project1.statuses.find(@complete.id).should == @complete
322
+ end
323
+
324
+ should "not work for id not in association" do
325
+ lambda {
326
+ @project1.statuses.find!(@archived.id)
327
+ }.should raise_error(MongoMapper::DocumentNotFound)
328
+ end
329
+ end
330
+
331
+ context "with multiple ids" do
332
+ should "work for ids in association" do
333
+ statuses = @project1.statuses.find(@brand_new.id, @complete.id)
334
+ statuses.should == [@brand_new, @complete]
335
+ end
336
+
337
+ should "not work for ids not in association" do
338
+ lambda {
339
+ @project1.statuses.find!(@brand_new.id, @complete.id, @archived.id)
340
+ }.should raise_error(MongoMapper::DocumentNotFound)
341
+ end
342
+ end
343
+
344
+ context "with #paginate" do
345
+ setup do
346
+ @statuses = @project2.statuses.paginate(:per_page => 2, :page => 1, :order => 'name asc')
347
+ end
348
+
349
+ should "return total pages" do
350
+ @statuses.total_pages.should == 2
351
+ end
352
+
353
+ should "return total entries" do
354
+ @statuses.total_entries.should == 3
355
+ end
356
+
357
+ should "return the subject" do
358
+ @statuses.collect(&:name).should == %w(Archived Complete)
359
+ end
360
+ end
361
+ end
362
+
363
+ context "extending the association" do
364
+ should "work using a block passed to many" do
365
+ project = Project.new(:name => "Some Project")
366
+ status1 = Status.new(:name => "New")
367
+ status2 = Status.new(:name => "Assigned")
368
+ status3 = Status.new(:name => "Closed")
369
+ project.statuses = [status1, status2, status3]
370
+ project.save
371
+
372
+ open_statuses = project.statuses.open
373
+ open_statuses.should include(status1)
374
+ open_statuses.should include(status2)
375
+ open_statuses.should_not include(status3)
376
+ end
377
+
378
+ should "work using many's :extend option" do
379
+ project = Project.new(:name => "Some Project")
380
+ collaborator1 = Collaborator.new(:name => "zing")
381
+ collaborator2 = Collaborator.new(:name => "zang")
382
+ project.collaborators = [collaborator1, collaborator2]
383
+ project.save
384
+ project.collaborators.top.should == collaborator1
385
+ end
386
+ end
387
+
388
+ context ":dependent" do
389
+ setup do
390
+ # FIXME: make use of already defined models
391
+ class ::Property
392
+ include MongoMapper::Document
393
+ end
394
+ Property.collection.remove
395
+
396
+ class ::Thing
397
+ include MongoMapper::Document
398
+ key :name, String
399
+ end
400
+ Thing.collection.remove
401
+ end
402
+
403
+ teardown do
404
+ Object.send :remove_const, 'Property' if defined?(::Property)
405
+ Object.send :remove_const, 'Thing' if defined?(::Thing)
406
+ end
407
+
408
+ context "=> destroy" do
409
+ setup do
410
+ Property.key :thing_id, ObjectId
411
+ Property.belongs_to :thing, :dependent => :destroy
412
+ Thing.many :properties, :dependent => :destroy
413
+
414
+ @thing = Thing.create(:name => "Tree")
415
+ @property1 = Property.create
416
+ @property2 = Property.create
417
+ @property3 = Property.create
418
+ @thing.properties << @property1
419
+ @thing.properties << @property2
420
+ @thing.properties << @property3
421
+ end
422
+
423
+ should "should destroy the associated documents" do
424
+ @thing.properties.count.should == 3
425
+ @thing.destroy
426
+ @thing.properties.count.should == 0
427
+ Property.count.should == 0
428
+ end
429
+ end
430
+
431
+ context "=> delete_all" do
432
+ setup do
433
+ Property.key :thing_id, ObjectId
434
+ Property.belongs_to :thing
435
+ Thing.has_many :properties, :dependent => :delete_all
436
+
437
+ @thing = Thing.create(:name => "Tree")
438
+ @property1 = Property.create
439
+ @property2 = Property.create
440
+ @property3 = Property.create
441
+ @thing.properties << @property1
442
+ @thing.properties << @property2
443
+ @thing.properties << @property3
444
+ end
445
+
446
+ should "should delete associated documents" do
447
+ @thing.properties.count.should == 3
448
+ @thing.destroy
449
+ @thing.properties.count.should == 0
450
+ Property.count.should == 0
451
+ end
452
+ end
453
+
454
+ context "=> nullify" do
455
+ setup do
456
+ Property.key :thing_id, ObjectId
457
+ Property.belongs_to :thing
458
+ Thing.has_many :properties, :dependent => :nullify
459
+
460
+ @thing = Thing.create(:name => "Tree")
461
+ @property1 = Property.create
462
+ @property2 = Property.create
463
+ @property3 = Property.create
464
+ @thing.properties << @property1
465
+ @thing.properties << @property2
466
+ @thing.properties << @property3
467
+ end
468
+
469
+ should "should nullify relationship but not destroy associated documents" do
470
+ @thing.properties.count.should == 3
471
+ @thing.destroy
472
+ @thing.properties.count.should == 0
473
+ Property.count.should == 3
474
+ end
475
+ end
476
+ end
477
+ end