danielharan-mongo_mapper 0.6.5

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 (74) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +53 -0
  4. data/Rakefile +55 -0
  5. data/VERSION +1 -0
  6. data/bin/mmconsole +60 -0
  7. data/lib/mongo_mapper.rb +134 -0
  8. data/lib/mongo_mapper/associations.rb +183 -0
  9. data/lib/mongo_mapper/associations/base.rb +110 -0
  10. data/lib/mongo_mapper/associations/belongs_to_polymorphic_proxy.rb +34 -0
  11. data/lib/mongo_mapper/associations/belongs_to_proxy.rb +22 -0
  12. data/lib/mongo_mapper/associations/many_documents_as_proxy.rb +25 -0
  13. data/lib/mongo_mapper/associations/many_documents_proxy.rb +127 -0
  14. data/lib/mongo_mapper/associations/many_embedded_polymorphic_proxy.rb +33 -0
  15. data/lib/mongo_mapper/associations/many_embedded_proxy.rb +53 -0
  16. data/lib/mongo_mapper/associations/many_polymorphic_proxy.rb +11 -0
  17. data/lib/mongo_mapper/associations/many_proxy.rb +6 -0
  18. data/lib/mongo_mapper/associations/proxy.rb +80 -0
  19. data/lib/mongo_mapper/callbacks.rb +109 -0
  20. data/lib/mongo_mapper/dirty.rb +136 -0
  21. data/lib/mongo_mapper/document.rb +481 -0
  22. data/lib/mongo_mapper/dynamic_finder.rb +35 -0
  23. data/lib/mongo_mapper/embedded_document.rb +386 -0
  24. data/lib/mongo_mapper/finder_options.rb +133 -0
  25. data/lib/mongo_mapper/key.rb +36 -0
  26. data/lib/mongo_mapper/observing.rb +50 -0
  27. data/lib/mongo_mapper/pagination.rb +53 -0
  28. data/lib/mongo_mapper/rails_compatibility/document.rb +15 -0
  29. data/lib/mongo_mapper/rails_compatibility/embedded_document.rb +27 -0
  30. data/lib/mongo_mapper/serialization.rb +54 -0
  31. data/lib/mongo_mapper/serializers/json_serializer.rb +92 -0
  32. data/lib/mongo_mapper/support.rb +193 -0
  33. data/lib/mongo_mapper/validations.rb +41 -0
  34. data/mongo_mapper.gemspec +171 -0
  35. data/specs.watchr +32 -0
  36. data/test/NOTE_ON_TESTING +1 -0
  37. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +55 -0
  38. data/test/functional/associations/test_belongs_to_proxy.rb +48 -0
  39. data/test/functional/associations/test_many_documents_as_proxy.rb +246 -0
  40. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +156 -0
  41. data/test/functional/associations/test_many_embedded_proxy.rb +196 -0
  42. data/test/functional/associations/test_many_polymorphic_proxy.rb +339 -0
  43. data/test/functional/associations/test_many_proxy.rb +384 -0
  44. data/test/functional/test_associations.rb +44 -0
  45. data/test/functional/test_binary.rb +18 -0
  46. data/test/functional/test_callbacks.rb +85 -0
  47. data/test/functional/test_dirty.rb +159 -0
  48. data/test/functional/test_document.rb +1180 -0
  49. data/test/functional/test_embedded_document.rb +125 -0
  50. data/test/functional/test_logger.rb +20 -0
  51. data/test/functional/test_pagination.rb +95 -0
  52. data/test/functional/test_rails_compatibility.rb +25 -0
  53. data/test/functional/test_string_id_compatibility.rb +72 -0
  54. data/test/functional/test_validations.rb +369 -0
  55. data/test/models.rb +271 -0
  56. data/test/support/custom_matchers.rb +55 -0
  57. data/test/support/timing.rb +16 -0
  58. data/test/test_helper.rb +27 -0
  59. data/test/unit/serializers/test_json_serializer.rb +189 -0
  60. data/test/unit/test_association_base.rb +166 -0
  61. data/test/unit/test_document.rb +204 -0
  62. data/test/unit/test_dynamic_finder.rb +125 -0
  63. data/test/unit/test_embedded_document.rb +718 -0
  64. data/test/unit/test_finder_options.rb +296 -0
  65. data/test/unit/test_key.rb +172 -0
  66. data/test/unit/test_mongo_mapper.rb +65 -0
  67. data/test/unit/test_observing.rb +101 -0
  68. data/test/unit/test_pagination.rb +113 -0
  69. data/test/unit/test_rails_compatibility.rb +49 -0
  70. data/test/unit/test_serializations.rb +52 -0
  71. data/test/unit/test_support.rb +342 -0
  72. data/test/unit/test_time_zones.rb +40 -0
  73. data/test/unit/test_validations.rb +503 -0
  74. metadata +233 -0
@@ -0,0 +1,125 @@
1
+ require 'test_helper'
2
+ require 'models'
3
+
4
+ class EmbeddedDocumentTest < Test::Unit::TestCase
5
+ def setup
6
+ @document = Class.new do
7
+ include MongoMapper::Document
8
+ set_collection_name 'users'
9
+
10
+ key :first_name, String
11
+ key :last_name, String
12
+ end
13
+ @document.collection.remove
14
+ end
15
+
16
+ context "Saving a document with an embedded document" do
17
+ setup do
18
+ @document.class_eval do
19
+ key :foo, Address
20
+ end
21
+
22
+ @address = Address.new(:city => 'South Bend', :state => 'IN')
23
+ @doc = @document.new(:foo => @address)
24
+ end
25
+
26
+ should "embed embedded document" do
27
+ @doc.save
28
+ @doc.foo.city.should == 'South Bend'
29
+ @doc.foo.state.should == 'IN'
30
+
31
+ doc = @doc.reload
32
+ doc.foo.city.should == 'South Bend'
33
+ doc.foo.state.should == 'IN'
34
+ end
35
+ end
36
+
37
+ context "Instantiating single collection inherited embedded documents" do
38
+ setup do
39
+ @document = Class.new do
40
+ include MongoMapper::Document
41
+ key :message, Message
42
+ end
43
+ end
44
+
45
+ should "work" do
46
+ doc1 = @document.create(:message => Enter.new)
47
+ doc2 = @document.create(:message => Exit.new)
48
+ doc3 = @document.create(:message => Chat.new)
49
+
50
+ doc1.reload.message.class.should be(Enter)
51
+ doc2.reload.message.class.should be(Exit)
52
+ doc3.reload.message.class.should be(Chat)
53
+ end
54
+ end
55
+
56
+ context "new?" do
57
+ setup do
58
+ @document.class_eval do
59
+ key :foo, Address
60
+ end
61
+ end
62
+
63
+ should "be new until document is saved" do
64
+ address = Address.new(:city => 'South Bend', :state => 'IN')
65
+ doc = @document.new(:foo => address)
66
+ address.new?.should == true
67
+ end
68
+
69
+ should "not be new after document is saved" do
70
+ address = Address.new(:city => 'South Bend', :state => 'IN')
71
+ doc = @document.new(:foo => address)
72
+ doc.save
73
+ doc.foo.new?.should == false
74
+ end
75
+
76
+ should "not be new when document is read back" do
77
+ address = Address.new(:city => 'South Bend', :state => 'IN')
78
+ doc = @document.new(:foo => address)
79
+ doc.save
80
+
81
+ doc = doc.reload
82
+ doc.foo.new?.should == false
83
+ end
84
+ end
85
+
86
+ context "save" do
87
+ should "save the root document" do
88
+ person = RealPerson.create
89
+
90
+ pet = Pet.new :name => 'sparky'
91
+ person.pets << pet
92
+ pet.save
93
+
94
+ person = person.reload
95
+ person.pets.first.should == pet
96
+ end
97
+
98
+ should "save new keys" do
99
+ person = RealPerson.new
100
+ person[:new_attribute] = 'foobar'
101
+ person.save
102
+
103
+ person = person.reload
104
+ person.new_attribute.should == 'foobar'
105
+ end
106
+ end
107
+
108
+ context "update_attributes" do
109
+ should "save the root document" do
110
+ person = RealPerson.create
111
+
112
+ pet = Pet.new(:name => 'sparky')
113
+ person.pets << pet
114
+ pet.save
115
+
116
+ person = person.reload
117
+ pet = person.pets.first
118
+ pet.update_attributes :name => 'koda'
119
+
120
+ person = person.reload
121
+ person.pets.first._id.should == pet._id
122
+ person.pets.first.name.should == 'koda'
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,20 @@
1
+ require 'test_helper'
2
+
3
+ class LoggerTest < Test::Unit::TestCase
4
+ context "with connection that has logger" do
5
+ setup do
6
+ @output = StringIO.new
7
+ @logger = Logger.new(@output)
8
+ MongoMapper.connection = Mongo::Connection.new('127.0.0.1', 27017, :logger => @logger)
9
+ end
10
+
11
+ should "be able to get access to that logger" do
12
+ MongoMapper.logger.should == @logger
13
+ end
14
+
15
+ should "be able to log messages" do
16
+ MongoMapper.logger.debug 'testing'
17
+ @output.string.include?('testing').should be_true
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,95 @@
1
+ require 'test_helper'
2
+
3
+ class PaginationTest < Test::Unit::TestCase
4
+ context "Paginating" do
5
+ setup do
6
+ @document = Class.new do
7
+ include MongoMapper::Document
8
+ set_collection_name 'users'
9
+
10
+ key :first_name, String
11
+ key :last_name, String
12
+ key :age, Integer
13
+
14
+ def self.per_page; 1 end
15
+ end
16
+ @document.collection.remove
17
+
18
+ @doc1 = @document.create({:first_name => 'John', :last_name => 'Nunemaker', :age => '27'})
19
+ @doc2 = @document.create({:first_name => 'Steve', :last_name => 'Smith', :age => '28'})
20
+ @doc3 = @document.create({:first_name => 'Steph', :last_name => 'Nunemaker', :age => '26'})
21
+ end
22
+
23
+ should "return the total pages" do
24
+ result = @document.paginate(:per_page => 2, :page => 1)
25
+ result.total_pages.should == 2
26
+ end
27
+
28
+ should "return the total pages when defaulting to the document class per_page" do
29
+ result = @document.paginate(:page => 1)
30
+ result.total_pages.should == 3
31
+ end
32
+
33
+ should "return the total of records" do
34
+ result = @document.paginate(:per_page => 2, :page => 1)
35
+ result.total_entries.should == 3
36
+ end
37
+
38
+ should "return the items" do
39
+ result = @document.paginate(:per_page => 2, :page => 1, :order => 'first_name')
40
+ result.size.should == 2
41
+ result.should == [@doc1, @doc3]
42
+ end
43
+
44
+ should "accept conditions" do
45
+ result = @document.paginate({
46
+ :last_name => 'Nunemaker',
47
+ :order => "age DESC",
48
+ :per_page => 2,
49
+ :page => 1,
50
+ })
51
+ result.should == [@doc1, @doc3]
52
+ result.first.age.should == 27
53
+
54
+ result = @document.paginate({
55
+ :conditions => {:last_name => 'Nunemaker'},
56
+ :order => "age DESC",
57
+ :per_page => 2,
58
+ :page => 1} )
59
+ result.should == [@doc1, @doc3]
60
+ result.first.age.should == 27
61
+ end
62
+
63
+ should "withstand rigor" do
64
+ result = @document.paginate({
65
+ :per_page => 1,
66
+ :page => 1,
67
+ :order => 'age desc',
68
+ :last_name => 'Nunemaker'
69
+ })
70
+ result.should == [@doc1]
71
+ result.total_entries.should == 2
72
+ result.total_pages.should == 2
73
+
74
+ result = @document.paginate({
75
+ :per_page => 1,
76
+ :page => 2,
77
+ :order => 'age desc',
78
+ :last_name => 'Nunemaker'
79
+ })
80
+ result.should == [@doc3]
81
+ result.total_entries.should == 2
82
+ result.total_pages.should == 2
83
+
84
+ result = @document.paginate({
85
+ :per_page => 2,
86
+ :page => 1,
87
+ :order => 'age desc',
88
+ :last_name => 'Nunemaker'
89
+ })
90
+ result.should == [@doc1, @doc3]
91
+ result.total_entries.should == 2
92
+ result.total_pages.should == 1
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,25 @@
1
+ require 'test_helper'
2
+
3
+ class TestRailsCompatibility < Test::Unit::TestCase
4
+ class Item
5
+ include MongoMapper::EmbeddedDocument
6
+ key :for_all, String
7
+ end
8
+
9
+ class Order
10
+ include MongoMapper::Document
11
+ many :items, :class_name => 'TestRailsCompatibility::Item'
12
+ key :order_only, String
13
+ end
14
+
15
+ context "Document" do
16
+ setup do
17
+ Order.collection.remove
18
+ end
19
+
20
+ should "alias new to new_record?" do
21
+ instance = Order.new
22
+ instance.new_record?.should == instance.new?
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,72 @@
1
+ require 'test_helper'
2
+
3
+ class StringIdCompatibilityTest < Test::Unit::TestCase
4
+ def setup
5
+ @note_class = Class.new do
6
+ include MongoMapper::EmbeddedDocument
7
+ key :_id, String
8
+ end
9
+
10
+ @task_class = Class.new do
11
+ include MongoMapper::Document
12
+ key :_id, String
13
+ key :project_id, String
14
+ belongs_to :project
15
+ end
16
+
17
+ @project_class = Class.new do
18
+ include MongoMapper::Document
19
+ key :_id, String
20
+ end
21
+
22
+ @task_class.belongs_to :project, :class => @project_class
23
+ @project_class.many :notes, :class => @note_class
24
+ @project_class.many :tasks, :class => @task_class, :foreign_key => 'project_id'
25
+
26
+ @project_class.collection.remove
27
+ @task_class.collection.remove
28
+ end
29
+
30
+ should "assign correct _id for documents" do
31
+ project = @project_class.create
32
+ project._id.should == project.id
33
+ project._id.should be_instance_of(String)
34
+ project.id.size.should == 24
35
+ lambda {
36
+ Mongo::ObjectID.from_string(project.id)
37
+ }.should_not raise_error
38
+ end
39
+
40
+ should "assign correct _id for embedded documents" do
41
+ note = @note_class.new
42
+ note.id.should == note._id
43
+ note.id.size.should == 24
44
+ end
45
+
46
+ should "find records" do
47
+ project = @project_class.create
48
+ @project_class.find(project.id).should == project
49
+ end
50
+
51
+ should "save embedded docs" do
52
+ n1 = @note_class.new
53
+ n2 = @note_class.new
54
+ n3 = @note_class.new
55
+ project = @project_class.create(:notes => [n1, n2, n3])
56
+
57
+ project = project.reload
58
+ project.notes.size.should == 3
59
+ project.notes.should == [n1, n2, n3]
60
+ end
61
+
62
+ should "be able to associate records" do
63
+ t1 = @task_class.new(:body => 'First task')
64
+ t2 = @task_class.new(:body => 'Second task')
65
+ t3 = @task_class.new(:body => 'Third task')
66
+ project = @project_class.create(:name => 'MM', :tasks => [t1, t2, t3])
67
+
68
+ project = project.reload
69
+ project.tasks.count.should == 3
70
+ project.tasks.should == [t1, t2, t3]
71
+ end
72
+ end
@@ -0,0 +1,369 @@
1
+ require 'test_helper'
2
+
3
+ class ValidationsTest < Test::Unit::TestCase
4
+ context "Saving a new document that is invalid" do
5
+ setup do
6
+ @document = Class.new do
7
+ include MongoMapper::Document
8
+ set_collection_name 'test'
9
+ key :name, String, :required => true
10
+ end
11
+ @document.collection.remove
12
+ end
13
+
14
+ should "not insert document" do
15
+ doc = @document.new
16
+ doc.save
17
+ @document.count.should == 0
18
+ end
19
+
20
+ should "populate document's errors" do
21
+ doc = @document.new
22
+ doc.errors.size.should == 0
23
+ doc.save
24
+ doc.errors.full_messages.should == ["Name can't be empty"]
25
+ end
26
+ end
27
+
28
+ context "Skipping validations when saving" do
29
+ setup do
30
+ @document = Class.new do
31
+ include MongoMapper::Document
32
+ set_collection_name 'test'
33
+ key :name, String, :required => true
34
+ end
35
+ @document.collection.remove
36
+ end
37
+
38
+ should "insert document" do
39
+ doc = @document.new
40
+ doc.save(false)
41
+ @document.count.should == 1
42
+ end
43
+ end
44
+
45
+ context "Saving a document that is invalid (destructive)" do
46
+ setup do
47
+ @document = Class.new do
48
+ include MongoMapper::Document
49
+ set_collection_name 'test'
50
+ key :name, String, :required => true
51
+ end
52
+ @document.collection.remove
53
+ end
54
+
55
+ should "raise error" do
56
+ doc = @document.new
57
+ lambda { doc.save! }.should raise_error(MongoMapper::DocumentNotValid)
58
+ end
59
+ end
60
+
61
+ context "Creating a document that is invalid (destructive)" do
62
+ setup do
63
+ @document = Class.new do
64
+ include MongoMapper::Document
65
+ set_collection_name 'test'
66
+ key :name, String, :required => true
67
+ end
68
+ @document.collection.remove
69
+ end
70
+
71
+ should "raise error" do
72
+ lambda { @document.create! }.should raise_error(MongoMapper::DocumentNotValid)
73
+ end
74
+
75
+ should "create a new document" do
76
+ instance = @document.create!(:name => "James")
77
+ instance.new_record?.should be_false
78
+ end
79
+ end
80
+
81
+ context "Saving an existing document that is invalid" do
82
+ setup do
83
+ @document = Class.new do
84
+ include MongoMapper::Document
85
+ set_collection_name 'test'
86
+ key :name, String, :required => true
87
+ end
88
+ @document.collection.remove
89
+
90
+ @doc = @document.create(:name => 'John Nunemaker')
91
+ end
92
+
93
+ should "not update document" do
94
+ @doc.name = nil
95
+ @doc.save
96
+ @doc.reload.name.should == 'John Nunemaker'
97
+ end
98
+
99
+ should "populate document's errors" do
100
+ @doc.name = nil
101
+ @doc.save
102
+ @doc.errors.full_messages.should == ["Name can't be empty"]
103
+ end
104
+ end
105
+
106
+ context "Adding validation errors" do
107
+ setup do
108
+ @document = Class.new do
109
+ include MongoMapper::Document
110
+ set_collection_name 'test'
111
+
112
+ key :action, String
113
+ def action_present
114
+ errors.add(:action, 'is invalid') if action.blank?
115
+ end
116
+ end
117
+ @document.collection.remove
118
+ end
119
+
120
+ should "work with validate_on_create callback" do
121
+ @document.validate_on_create :action_present
122
+
123
+ doc = @document.new
124
+ doc.action = nil
125
+ doc.should have_error_on(:action)
126
+
127
+ doc.action = 'kick'
128
+ doc.should_not have_error_on(:action)
129
+ doc.save
130
+
131
+ doc.action = nil
132
+ doc.should_not have_error_on(:action)
133
+ end
134
+
135
+ should "work with validate_on_update callback" do
136
+ @document.validate_on_update :action_present
137
+
138
+ doc = @document.new
139
+ doc.action = nil
140
+ doc.should_not have_error_on(:action)
141
+ doc.save
142
+
143
+ doc.action = nil
144
+ doc.should have_error_on(:action)
145
+
146
+ doc.action = 'kick'
147
+ doc.should_not have_error_on(:action)
148
+ end
149
+ end
150
+
151
+ context "validating uniqueness of" do
152
+ setup do
153
+ @document = Class.new do
154
+ include MongoMapper::Document
155
+ set_collection_name 'test'
156
+
157
+ key :name, String
158
+ validates_uniqueness_of :name
159
+ end
160
+ @document.collection.remove
161
+ end
162
+
163
+ should "not fail if object is new" do
164
+ doc = @document.new
165
+ doc.should_not have_error_on(:name)
166
+ end
167
+
168
+ should "not fail when new object is out of scope" do
169
+ document = Class.new do
170
+ include MongoMapper::Document
171
+ set_collection_name 'test'
172
+
173
+ key :name
174
+ key :adult
175
+ validates_uniqueness_of :name, :scope => :adult
176
+ end
177
+ doc = document.new("name" => "joe", :adult => true)
178
+ doc.save.should be_true
179
+
180
+ doc2 = document.new("name" => "joe", :adult => false)
181
+ doc2.should be_valid
182
+
183
+ end
184
+
185
+ should "allow to update an object" do
186
+ doc = @document.new("name" => "joe")
187
+ doc.save.should be_true
188
+
189
+ @document \
190
+ .stubs(:first) \
191
+ .with(:name => 'joe') \
192
+ .returns(doc)
193
+
194
+ doc.name = "joe"
195
+ doc.valid?.should be_true
196
+ doc.should_not have_error_on(:name)
197
+ end
198
+
199
+ should "fail if object name is not unique" do
200
+ doc = @document.new("name" => "joe")
201
+ doc.save.should be_true
202
+
203
+ @document \
204
+ .stubs(:first) \
205
+ .with(:name => 'joe') \
206
+ .returns(doc)
207
+
208
+ doc2 = @document.new("name" => "joe")
209
+ doc2.should have_error_on(:name)
210
+ end
211
+
212
+ should "allow multiple blank entries if :allow_blank => true" do
213
+ document = Class.new do
214
+ include MongoMapper::Document
215
+ set_collection_name 'test'
216
+
217
+ key :name
218
+ validates_uniqueness_of :name, :allow_blank => :true
219
+ end
220
+
221
+ doc = document.new("name" => "")
222
+ doc.save.should be_true
223
+
224
+ document \
225
+ .stubs(:first) \
226
+ .with(:name => '') \
227
+ .returns(doc)
228
+
229
+ doc2 = document.new("name" => "")
230
+ doc2.should_not have_error_on(:name)
231
+ end
232
+
233
+ should "allow entries that differ only in case by default" do
234
+ document = Class.new do
235
+ include MongoMapper::Document
236
+ set_collection_name 'test'
237
+
238
+ key :name
239
+ validates_uniqueness_of :name
240
+ end
241
+
242
+ doc = document.new("name" => "BLAMMO")
243
+ doc.save.should be_true
244
+
245
+ doc2 = document.new("name" => "blammo")
246
+ doc2.should_not have_error_on(:name)
247
+ end
248
+
249
+ should "fail on entries that differ only in case if :case_sensitive => false" do
250
+ document = Class.new do
251
+ include MongoMapper::Document
252
+ set_collection_name 'test'
253
+
254
+ key :name
255
+ validates_uniqueness_of :name, :case_sensitive => false
256
+ end
257
+
258
+ doc = document.new("name" => "BLAMMO")
259
+ doc.save.should be_true
260
+
261
+ doc2 = document.new("name" => "blammo")
262
+ doc2.should have_error_on(:name)
263
+ end
264
+
265
+ context "scoped by a single attribute" do
266
+ setup do
267
+ @document = Class.new do
268
+ include MongoMapper::Document
269
+ set_collection_name 'test'
270
+
271
+ key :name, String
272
+ key :scope, String
273
+ validates_uniqueness_of :name, :scope => :scope
274
+ end
275
+ @document.collection.remove
276
+ end
277
+
278
+ should "fail if the same name exists in the scope" do
279
+ doc = @document.new("name" => "joe", "scope" => "one")
280
+ doc.save.should be_true
281
+
282
+ @document \
283
+ .stubs(:first) \
284
+ .with(:name => 'joe', :scope => "one") \
285
+ .returns(doc)
286
+
287
+ doc2 = @document.new("name" => "joe", "scope" => "one")
288
+ doc2.should have_error_on(:name)
289
+ end
290
+
291
+ should "pass if the same name exists in a different scope" do
292
+ doc = @document.new("name" => "joe", "scope" => "one")
293
+ doc.save.should be_true
294
+
295
+ @document \
296
+ .stubs(:first) \
297
+ .with(:name => 'joe', :scope => 'two') \
298
+ .returns(nil)
299
+
300
+ doc2 = @document.new("name" => "joe", "scope" => "two")
301
+ doc2.should_not have_error_on(:name)
302
+ end
303
+ end
304
+
305
+ context "scoped by a multiple attributes" do
306
+ setup do
307
+ @document = Class.new do
308
+ include MongoMapper::Document
309
+ set_collection_name 'test'
310
+
311
+ key :name, String
312
+ key :first_scope, String
313
+ key :second_scope, String
314
+ validates_uniqueness_of :name, :scope => [:first_scope, :second_scope]
315
+ end
316
+ @document.collection.remove
317
+ end
318
+
319
+ should "fail if the same name exists in the scope" do
320
+ doc = @document.new("name" => "joe", "first_scope" => "one", "second_scope" => "two")
321
+ doc.save.should be_true
322
+
323
+ @document \
324
+ .stubs(:first) \
325
+ .with(:name => 'joe', :first_scope => 'one', :second_scope => 'two') \
326
+ .returns(doc)
327
+
328
+ doc2 = @document.new("name" => "joe", "first_scope" => "one", "second_scope" => "two")
329
+ doc2.should have_error_on(:name)
330
+ end
331
+
332
+ should "pass if the same name exists in a different scope" do
333
+ doc = @document.new("name" => "joe", "first_scope" => "one", "second_scope" => "two")
334
+ doc.save.should be_true
335
+
336
+ @document \
337
+ .stubs(:first) \
338
+ .with(:name => 'joe', :first_scope => 'one', :second_scope => 'one') \
339
+ .returns(nil)
340
+
341
+ doc2 = @document.new("name" => "joe", "first_scope" => "one", "second_scope" => "one")
342
+ doc2.should_not have_error_on(:name)
343
+ end
344
+ end
345
+ end
346
+
347
+ context "validates uniqueness of with :unique shortcut" do
348
+ should "work" do
349
+ @document = Class.new do
350
+ include MongoMapper::Document
351
+ set_collection_name 'test'
352
+
353
+ key :name, String, :unique => true
354
+ end
355
+ @document.collection.remove
356
+
357
+ doc = @document.create(:name => 'John')
358
+ doc.should_not have_error_on(:name)
359
+
360
+ @document \
361
+ .stubs(:first) \
362
+ .with(:name => 'John') \
363
+ .returns(doc)
364
+
365
+ second_john = @document.create(:name => 'John')
366
+ second_john.should have_error_on(:name, 'has already been taken')
367
+ end
368
+ end
369
+ end