numon 0.0.1

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 (99) hide show
  1. data/.gitignore +10 -0
  2. data/LICENSE +20 -0
  3. data/README.rdoc +31 -0
  4. data/Rakefile +52 -0
  5. data/bin/mmconsole +60 -0
  6. data/lib/mongo_mapper.rb +138 -0
  7. data/lib/mongo_mapper/document.rb +359 -0
  8. data/lib/mongo_mapper/embedded_document.rb +61 -0
  9. data/lib/mongo_mapper/plugins.rb +34 -0
  10. data/lib/mongo_mapper/plugins/associations.rb +105 -0
  11. data/lib/mongo_mapper/plugins/associations/base.rb +123 -0
  12. data/lib/mongo_mapper/plugins/associations/belongs_to_polymorphic_proxy.rb +30 -0
  13. data/lib/mongo_mapper/plugins/associations/belongs_to_proxy.rb +25 -0
  14. data/lib/mongo_mapper/plugins/associations/collection.rb +21 -0
  15. data/lib/mongo_mapper/plugins/associations/embedded_collection.rb +39 -0
  16. data/lib/mongo_mapper/plugins/associations/in_array_proxy.rb +144 -0
  17. data/lib/mongo_mapper/plugins/associations/many_documents_as_proxy.rb +28 -0
  18. data/lib/mongo_mapper/plugins/associations/many_documents_proxy.rb +120 -0
  19. data/lib/mongo_mapper/plugins/associations/many_embedded_polymorphic_proxy.rb +31 -0
  20. data/lib/mongo_mapper/plugins/associations/many_embedded_proxy.rb +23 -0
  21. data/lib/mongo_mapper/plugins/associations/many_polymorphic_proxy.rb +13 -0
  22. data/lib/mongo_mapper/plugins/associations/one_proxy.rb +68 -0
  23. data/lib/mongo_mapper/plugins/associations/proxy.rb +118 -0
  24. data/lib/mongo_mapper/plugins/callbacks.rb +234 -0
  25. data/lib/mongo_mapper/plugins/clone.rb +13 -0
  26. data/lib/mongo_mapper/plugins/descendants.rb +16 -0
  27. data/lib/mongo_mapper/plugins/dirty.rb +119 -0
  28. data/lib/mongo_mapper/plugins/equality.rb +23 -0
  29. data/lib/mongo_mapper/plugins/identity_map.rb +122 -0
  30. data/lib/mongo_mapper/plugins/inspect.rb +14 -0
  31. data/lib/mongo_mapper/plugins/keys.rb +336 -0
  32. data/lib/mongo_mapper/plugins/logger.rb +17 -0
  33. data/lib/mongo_mapper/plugins/modifiers.rb +87 -0
  34. data/lib/mongo_mapper/plugins/pagination.rb +24 -0
  35. data/lib/mongo_mapper/plugins/pagination/proxy.rb +72 -0
  36. data/lib/mongo_mapper/plugins/protected.rb +45 -0
  37. data/lib/mongo_mapper/plugins/rails.rb +53 -0
  38. data/lib/mongo_mapper/plugins/serialization.rb +75 -0
  39. data/lib/mongo_mapper/plugins/timestamps.rb +21 -0
  40. data/lib/mongo_mapper/plugins/userstamps.rb +14 -0
  41. data/lib/mongo_mapper/plugins/validations.rb +46 -0
  42. data/lib/mongo_mapper/query.rb +130 -0
  43. data/lib/mongo_mapper/support.rb +216 -0
  44. data/lib/mongo_mapper/support/descendant_appends.rb +46 -0
  45. data/lib/mongo_mapper/support/find.rb +77 -0
  46. data/lib/mongo_mapper/version.rb +3 -0
  47. data/numon.gemspec +207 -0
  48. data/performance/read_write.rb +52 -0
  49. data/specs.watchr +51 -0
  50. data/test/NOTE_ON_TESTING +1 -0
  51. data/test/active_model_lint_test.rb +11 -0
  52. data/test/functional/associations/test_belongs_to_polymorphic_proxy.rb +63 -0
  53. data/test/functional/associations/test_belongs_to_proxy.rb +101 -0
  54. data/test/functional/associations/test_in_array_proxy.rb +325 -0
  55. data/test/functional/associations/test_many_documents_as_proxy.rb +229 -0
  56. data/test/functional/associations/test_many_documents_proxy.rb +453 -0
  57. data/test/functional/associations/test_many_embedded_polymorphic_proxy.rb +176 -0
  58. data/test/functional/associations/test_many_embedded_proxy.rb +256 -0
  59. data/test/functional/associations/test_many_polymorphic_proxy.rb +302 -0
  60. data/test/functional/associations/test_one_proxy.rb +161 -0
  61. data/test/functional/test_associations.rb +44 -0
  62. data/test/functional/test_binary.rb +27 -0
  63. data/test/functional/test_callbacks.rb +151 -0
  64. data/test/functional/test_dirty.rb +163 -0
  65. data/test/functional/test_document.rb +1165 -0
  66. data/test/functional/test_embedded_document.rb +130 -0
  67. data/test/functional/test_identity_map.rb +508 -0
  68. data/test/functional/test_indexing.rb +44 -0
  69. data/test/functional/test_logger.rb +20 -0
  70. data/test/functional/test_modifiers.rb +322 -0
  71. data/test/functional/test_pagination.rb +93 -0
  72. data/test/functional/test_protected.rb +161 -0
  73. data/test/functional/test_string_id_compatibility.rb +67 -0
  74. data/test/functional/test_timestamps.rb +64 -0
  75. data/test/functional/test_userstamps.rb +28 -0
  76. data/test/functional/test_validations.rb +329 -0
  77. data/test/models.rb +232 -0
  78. data/test/support/custom_matchers.rb +55 -0
  79. data/test/support/timing.rb +16 -0
  80. data/test/test_helper.rb +61 -0
  81. data/test/unit/associations/test_base.rb +207 -0
  82. data/test/unit/associations/test_proxy.rb +105 -0
  83. data/test/unit/serializers/test_json_serializer.rb +202 -0
  84. data/test/unit/test_descendant_appends.rb +71 -0
  85. data/test/unit/test_document.rb +231 -0
  86. data/test/unit/test_dynamic_finder.rb +123 -0
  87. data/test/unit/test_embedded_document.rb +663 -0
  88. data/test/unit/test_keys.rb +173 -0
  89. data/test/unit/test_mongo_mapper.rb +155 -0
  90. data/test/unit/test_pagination.rb +160 -0
  91. data/test/unit/test_plugins.rb +50 -0
  92. data/test/unit/test_query.rb +340 -0
  93. data/test/unit/test_rails.rb +123 -0
  94. data/test/unit/test_rails_compatibility.rb +52 -0
  95. data/test/unit/test_serialization.rb +51 -0
  96. data/test/unit/test_support.rb +366 -0
  97. data/test/unit/test_time_zones.rb +39 -0
  98. data/test/unit/test_validations.rb +544 -0
  99. metadata +305 -0
@@ -0,0 +1,161 @@
1
+ require 'test_helper'
2
+
3
+ class ProtectedTest < Test::Unit::TestCase
4
+ context 'A document with protected attributes' do
5
+ setup do
6
+ @doc_class = Doc do
7
+ key :name, String
8
+ key :admin, Boolean, :default => false
9
+
10
+ attr_protected :admin
11
+ end
12
+
13
+ @doc = @doc_class.create(:name => 'Steve Sloan')
14
+ end
15
+
16
+ should 'have protected attributes class method' do
17
+ @doc_class.protected_attributes.should == [:admin].to_set
18
+ end
19
+
20
+ should "default protected attributes to nil" do
21
+ Doc().protected_attributes.should be_nil
22
+ end
23
+
24
+ should "have protected attributes instance method" do
25
+ @doc.protected_attributes.should equal(@doc_class.protected_attributes)
26
+ end
27
+
28
+ should "work with :protected shortcut when defining key" do
29
+ Doc() do
30
+ key :user_id, ObjectId, :protected => true
31
+ end.protected_attributes.should == [:user_id].to_set
32
+ end
33
+
34
+ should 'assign protected attribute through accessor' do
35
+ @doc.admin = true
36
+ @doc.admin.should be_true
37
+ end
38
+
39
+ should "ignore protected attribute on #initialize" do
40
+ doc = @doc_class.new(:name => 'John', :admin => true)
41
+ doc.admin.should be_false
42
+ doc.name.should == 'John'
43
+ end
44
+
45
+ should "not ignore protected attributes on #initialize from the database" do
46
+ doc = @doc_class.new(:name => 'John')
47
+ doc.admin = true
48
+ doc.save!
49
+
50
+ doc = @doc_class.first(:name => 'John')
51
+ doc.admin.should be_true
52
+ doc.name.should == 'John'
53
+ end
54
+
55
+ should 'ignore protected attribute on #update_attributes' do
56
+ @doc.update_attributes(:name => 'Ren Hoek', :admin => true)
57
+ @doc.name.should == 'Ren Hoek'
58
+ @doc.admin.should be_false
59
+ end
60
+
61
+ should 'ignore protected attribute on #update_attributes!' do
62
+ @doc.update_attributes!(:name => 'Stimpson J. Cat', :admin => true)
63
+ @doc.name.should == 'Stimpson J. Cat'
64
+ @doc.admin.should be_false
65
+ end
66
+
67
+ should 'be indifferent to whether the protected keys are strings or symbols' do
68
+ @doc.update_attributes!("name" => 'Stimpson J. Cat', "admin" => true)
69
+ @doc.name.should == 'Stimpson J. Cat'
70
+ @doc.admin.should be_false
71
+ end
72
+ end
73
+
74
+ context "Single collection inherited protected attributes" do
75
+ setup do
76
+ class ::GrandParent
77
+ include MongoMapper::Document
78
+
79
+ key :_type, String
80
+ key :site_id, ObjectId
81
+
82
+ attr_protected :site_id
83
+ end
84
+ GrandParent.collection.remove
85
+
86
+ class ::Child < ::GrandParent
87
+ key :position, Integer
88
+
89
+ attr_protected :position
90
+ end
91
+
92
+ class ::GrandChild < ::Child; end
93
+
94
+ class ::OtherChild < ::GrandParent
95
+ key :blog_id, ObjectId
96
+
97
+ attr_protected :blog_id
98
+ end
99
+ end
100
+
101
+ teardown do
102
+ Object.send :remove_const, 'GrandParent' if defined?(::GrandParent)
103
+ Object.send :remove_const, 'Child' if defined?(::Child)
104
+ Object.send :remove_const, 'GrandChild' if defined?(::GrandChild)
105
+ Object.send :remove_const, 'OtherChild' if defined?(::OtherChild)
106
+ end
107
+
108
+ should "share keys down the inheritance trail" do
109
+ GrandParent.protected_attributes.should == [:site_id].to_set
110
+ Child.protected_attributes.should == [:site_id, :position].to_set
111
+ GrandChild.protected_attributes.should == [:site_id, :position].to_set
112
+ OtherChild.protected_attributes.should == [:site_id, :blog_id].to_set
113
+ end
114
+ end
115
+
116
+ context 'An embedded document with protected attributes' do
117
+ setup do
118
+ @doc_class = Doc('Project')
119
+ @edoc_class = EDoc('Person') do
120
+ key :name, String
121
+ key :admin, Boolean, :default => false
122
+
123
+ attr_protected :admin
124
+ end
125
+ @doc_class.many :people, :class => @edoc_class
126
+
127
+ @doc = @doc_class.create(:title => 'MongoMapper')
128
+ @edoc = @edoc_class.new(:name => 'Steve Sloan')
129
+ @doc.people << @edoc
130
+ end
131
+
132
+ should 'have protected attributes class method' do
133
+ @edoc_class.protected_attributes.should == [:admin].to_set
134
+ end
135
+
136
+ should "default protected attributes to nil" do
137
+ EDoc().protected_attributes.should be_nil
138
+ end
139
+
140
+ should "have protected attributes instance method" do
141
+ @edoc.protected_attributes.should equal(@edoc_class.protected_attributes)
142
+ end
143
+
144
+ should 'assign protected attribute through accessor' do
145
+ @edoc.admin = true
146
+ @edoc.admin.should be_true
147
+ end
148
+
149
+ should 'ignore protected attribute on #update_attributes' do
150
+ @edoc.update_attributes(:name => 'Ren Hoek', :admin => true)
151
+ @edoc.name.should == 'Ren Hoek'
152
+ @edoc.admin.should be_false
153
+ end
154
+
155
+ should 'ignore protected attribute on #update_attributes!' do
156
+ @edoc.update_attributes!(:name => 'Stimpson J. Cat', :admin => true)
157
+ @edoc.name.should == 'Stimpson J. Cat'
158
+ @edoc.admin.should be_false
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,67 @@
1
+ require 'test_helper'
2
+
3
+ class StringIdCompatibilityTest < Test::Unit::TestCase
4
+ def setup
5
+ @note_class = EDoc do
6
+ key :_id, String
7
+ end
8
+
9
+ @task_class = Doc do
10
+ key :_id, String
11
+ key :project_id, String
12
+ belongs_to :project
13
+ end
14
+
15
+ @project_class = Doc do
16
+ include MongoMapper::Document
17
+ key :_id, String
18
+ end
19
+
20
+ @task_class.belongs_to :project, :class => @project_class
21
+ @project_class.many :notes, :class => @note_class
22
+ @project_class.many :tasks, :class => @task_class, :foreign_key => 'project_id', :order => :position.asc
23
+ end
24
+
25
+ should "assign correct _id for documents" do
26
+ project = @project_class.create
27
+ project._id.should == project.id
28
+ project._id.should be_instance_of(String)
29
+ project.id.size.should == 24
30
+ lambda {
31
+ Mongo::ObjectID.from_string(project.id)
32
+ }.should_not raise_error
33
+ end
34
+
35
+ should "assign correct _id for embedded documents" do
36
+ note = @note_class.new
37
+ note.id.should == note._id
38
+ note.id.size.should == 24
39
+ end
40
+
41
+ should "find records" do
42
+ project = @project_class.create
43
+ @project_class.find(project.id).should == project
44
+ end
45
+
46
+ should "save embedded docs" do
47
+ n1 = @note_class.new
48
+ n2 = @note_class.new
49
+ n3 = @note_class.new
50
+ project = @project_class.create(:notes => [n1, n2, n3])
51
+
52
+ project = project.reload
53
+ project.notes.size.should == 3
54
+ project.notes.should == [n1, n2, n3]
55
+ end
56
+
57
+ should "be able to associate records" do
58
+ t1 = @task_class.new(:body => 'First task', :position => 1)
59
+ t2 = @task_class.new(:body => 'Second task', :position => 2)
60
+ t3 = @task_class.new(:body => 'Third task', :position => 3)
61
+ project = @project_class.create(:name => 'MM', :tasks => [t1, t2, t3])
62
+
63
+ project = project.reload
64
+ project.tasks.count.should == 3
65
+ project.tasks.should == [t1, t2, t3]
66
+ end
67
+ end
@@ -0,0 +1,64 @@
1
+ require 'test_helper'
2
+
3
+ class TimestampsTest < Test::Unit::TestCase
4
+ context "timestamping" do
5
+ setup do
6
+ @klass = Doc do
7
+ set_collection_name 'users'
8
+
9
+ key :first_name, String
10
+ key :last_name, String
11
+ key :age, Integer
12
+ key :date, Date
13
+ end
14
+ @klass.timestamps!
15
+ end
16
+
17
+ should "set created_at and updated_at on create" do
18
+ doc = @klass.new(:first_name => 'John', :age => 27)
19
+ doc.created_at.should be(nil)
20
+ doc.updated_at.should be(nil)
21
+ doc.save
22
+ doc.created_at.should_not be(nil)
23
+ doc.updated_at.should_not be(nil)
24
+ end
25
+
26
+ should "not overwrite created_at if it already exists" do
27
+ original_created_at = 1.month.ago
28
+ doc = @klass.new(:first_name => 'John', :age => 27, :created_at => original_created_at)
29
+ doc.created_at.to_i.should == original_created_at.to_i
30
+ doc.updated_at.should be_nil
31
+ doc.save
32
+ doc.created_at.to_i.should == original_created_at.to_i
33
+ doc.updated_at.should_not be_nil
34
+ end
35
+
36
+ should "set updated_at on field update but leave created_at alone" do
37
+ doc = @klass.create(:first_name => 'John', :age => 27)
38
+ old_created_at = doc.created_at
39
+ old_updated_at = doc.updated_at
40
+ doc.first_name = 'Johnny'
41
+
42
+ Timecop.freeze(Time.now + 5.seconds) do
43
+ doc.save
44
+ end
45
+
46
+ doc.created_at.should == old_created_at
47
+ doc.updated_at.should_not == old_updated_at
48
+ end
49
+
50
+ should "set updated_at on document update but leave created_at alone" do
51
+ doc = @klass.create(:first_name => 'John', :age => 27)
52
+ old_created_at = doc.created_at
53
+ old_updated_at = doc.updated_at
54
+
55
+ Timecop.freeze(Time.now + 5.seconds) do
56
+ @klass.update(doc._id, { :first_name => 'Johnny' })
57
+ end
58
+
59
+ doc = doc.reload
60
+ doc.created_at.should == old_created_at
61
+ doc.updated_at.should_not == old_updated_at
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,28 @@
1
+ require 'test_helper'
2
+
3
+ class UserstampsTest < Test::Unit::TestCase
4
+ context "userstamping" do
5
+ setup do
6
+ @document = Doc do
7
+ set_collection_name 'users'
8
+ userstamps!
9
+ end
10
+ end
11
+
12
+ should "add creator_id key" do
13
+ @document.keys.keys.should include('creator_id')
14
+ end
15
+
16
+ should "add updater_id key" do
17
+ @document.keys.keys.should include('updater_id')
18
+ end
19
+
20
+ should "add belongs_to creator" do
21
+ @document.associations.keys.should include('creator')
22
+ end
23
+
24
+ should "add belongs_to updater" do
25
+ @document.associations.keys.should include('updater')
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,329 @@
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 = Doc do
7
+ key :name, String, :required => true
8
+ end
9
+ end
10
+
11
+ should "not insert document" do
12
+ doc = @document.new
13
+ doc.save
14
+ @document.count.should == 0
15
+ end
16
+
17
+ should "populate document's errors" do
18
+ doc = @document.new
19
+ doc.errors.size.should == 0
20
+ doc.save
21
+ doc.errors.full_messages.should == ["Name can't be empty"]
22
+ end
23
+ end
24
+
25
+ context "Saving a document that is invalid (destructive)" do
26
+ setup do
27
+ @document = Doc do
28
+ key :name, String, :required => true
29
+ end
30
+ end
31
+
32
+ should "raise error" do
33
+ doc = @document.new
34
+ lambda { doc.save! }.should raise_error(MongoMapper::DocumentNotValid)
35
+ end
36
+ end
37
+
38
+ context "Creating a document that is invalid (destructive)" do
39
+ setup do
40
+ @document = Doc do
41
+ key :name, String, :required => true
42
+ end
43
+ end
44
+
45
+ should "raise error" do
46
+ lambda { @document.create! }.should raise_error(MongoMapper::DocumentNotValid)
47
+ end
48
+
49
+ should "create a new document" do
50
+ instance = @document.create!(:name => "James")
51
+ instance.new_record?.should be_false
52
+ end
53
+ end
54
+
55
+ context "Saving an existing document that is invalid" do
56
+ setup do
57
+ @document = Doc do
58
+ key :name, String, :required => true
59
+ end
60
+
61
+ @doc = @document.create(:name => 'John Nunemaker')
62
+ end
63
+
64
+ should "not update document" do
65
+ @doc.name = nil
66
+ @doc.save
67
+ @doc.reload.name.should == 'John Nunemaker'
68
+ end
69
+
70
+ should "populate document's errors" do
71
+ @doc.name = nil
72
+ @doc.save
73
+ @doc.errors.full_messages.should == ["Name can't be empty"]
74
+ end
75
+ end
76
+
77
+ context "Adding validation errors" do
78
+ setup do
79
+ @document = Doc do
80
+ key :action, String
81
+ def action_present
82
+ errors.add(:action, 'is invalid') if action.blank?
83
+ end
84
+ end
85
+ end
86
+
87
+ should "work with validate_on_create callback" do
88
+ @document.validate_on_create :action_present
89
+
90
+ doc = @document.new
91
+ doc.action = nil
92
+ doc.should have_error_on(:action)
93
+
94
+ doc.action = 'kick'
95
+ doc.should_not have_error_on(:action)
96
+ doc.save
97
+
98
+ doc.action = nil
99
+ doc.should_not have_error_on(:action)
100
+ end
101
+
102
+ should "work with validate_on_update callback" do
103
+ @document.validate_on_update :action_present
104
+
105
+ doc = @document.new
106
+ doc.action = nil
107
+ doc.should_not have_error_on(:action)
108
+ doc.save
109
+
110
+ doc.action = nil
111
+ doc.should have_error_on(:action)
112
+
113
+ doc.action = 'kick'
114
+ doc.should_not have_error_on(:action)
115
+ end
116
+ end
117
+
118
+ context "validating uniqueness of" do
119
+ setup do
120
+ @document = Doc do
121
+ key :name, String
122
+ validates_uniqueness_of :name
123
+ end
124
+ end
125
+
126
+ should "not fail if object is new" do
127
+ doc = @document.new
128
+ doc.should_not have_error_on(:name)
129
+ end
130
+
131
+ should "not fail when new object is out of scope" do
132
+ document = Doc do
133
+ key :name
134
+ key :adult
135
+ validates_uniqueness_of :name, :scope => :adult
136
+ end
137
+ doc = document.new("name" => "joe", :adult => true)
138
+ doc.save.should be_true
139
+
140
+ doc2 = document.new("name" => "joe", :adult => false)
141
+ doc2.should be_valid
142
+ end
143
+
144
+ should "allow to update an object" do
145
+ doc = @document.new("name" => "joe")
146
+ doc.save.should be_true
147
+
148
+ @document \
149
+ .stubs(:first) \
150
+ .with(:name => 'joe') \
151
+ .returns(doc)
152
+
153
+ doc.name = "joe"
154
+ doc.valid?.should be_true
155
+ doc.should_not have_error_on(:name)
156
+ end
157
+
158
+ should "fail if object name is not unique" do
159
+ doc = @document.new("name" => "joe")
160
+ doc.save.should be_true
161
+
162
+ @document \
163
+ .stubs(:first) \
164
+ .with(:name => 'joe') \
165
+ .returns(doc)
166
+
167
+ doc2 = @document.new("name" => "joe")
168
+ doc2.should have_error_on(:name)
169
+ end
170
+
171
+ should "allow multiple blank entries if :allow_blank => true" do
172
+ document = Doc do
173
+ key :name
174
+ validates_uniqueness_of :name, :allow_blank => :true
175
+ end
176
+
177
+ doc = document.new("name" => "")
178
+ doc.save.should be_true
179
+
180
+ document \
181
+ .stubs(:first) \
182
+ .with(:name => '') \
183
+ .returns(doc)
184
+
185
+ doc2 = document.new("name" => "")
186
+ doc2.should_not have_error_on(:name)
187
+ end
188
+
189
+ should "allow multiple nil entries if :allow_nil => true" do
190
+ document = Doc do
191
+ key :name
192
+ validates_uniqueness_of :name, :allow_nil => :true
193
+ end
194
+
195
+ doc = document.new('name' => nil)
196
+ doc.save.should be_true
197
+
198
+ doc2 = document.new('name' => nil)
199
+ doc2.should_not have_error_on(:name)
200
+ end
201
+
202
+ should "allow entries that differ only in case by default" do
203
+ document = Doc do
204
+ key :name
205
+ validates_uniqueness_of :name
206
+ end
207
+
208
+ doc = document.new("name" => "BLAMMO")
209
+ doc.save.should be_true
210
+
211
+ doc2 = document.new("name" => "blammo")
212
+ doc2.should_not have_error_on(:name)
213
+ end
214
+
215
+ context "with :case_sensitive => false" do
216
+ setup do
217
+ @document = Doc do
218
+ key :name
219
+ validates_uniqueness_of :name, :case_sensitive => false
220
+ end
221
+ end
222
+
223
+ should "fail on entries that differ only in case" do
224
+ doc = @document.new("name" => "BLAMMO")
225
+ doc.save.should be_true
226
+
227
+ doc2 = @document.new("name" => "blammo")
228
+ doc2.should have_error_on(:name)
229
+ end
230
+
231
+ should "not raise an error if value is nil" do
232
+ doc = @document.new("name" => nil)
233
+ lambda { doc.valid? }.should_not raise_error
234
+ end
235
+ end
236
+
237
+ context "scoped by a single attribute" do
238
+ setup do
239
+ @document = Doc do
240
+ key :name, String
241
+ key :scope, String
242
+ validates_uniqueness_of :name, :scope => :scope
243
+ end
244
+ end
245
+
246
+ should "fail if the same name exists in the scope" do
247
+ doc = @document.new("name" => "joe", "scope" => "one")
248
+ doc.save.should be_true
249
+
250
+ @document \
251
+ .stubs(:first) \
252
+ .with(:name => 'joe', :scope => "one") \
253
+ .returns(doc)
254
+
255
+ doc2 = @document.new("name" => "joe", "scope" => "one")
256
+ doc2.should have_error_on(:name)
257
+ end
258
+
259
+ should "pass if the same name exists in a different scope" do
260
+ doc = @document.new("name" => "joe", "scope" => "one")
261
+ doc.save.should be_true
262
+
263
+ @document \
264
+ .stubs(:first) \
265
+ .with(:name => 'joe', :scope => 'two') \
266
+ .returns(nil)
267
+
268
+ doc2 = @document.new("name" => "joe", "scope" => "two")
269
+ doc2.should_not have_error_on(:name)
270
+ end
271
+ end
272
+
273
+ context "scoped by a multiple attributes" do
274
+ setup do
275
+ @document = Doc do
276
+ key :name, String
277
+ key :first_scope, String
278
+ key :second_scope, String
279
+ validates_uniqueness_of :name, :scope => [:first_scope, :second_scope]
280
+ end
281
+ end
282
+
283
+ should "fail if the same name exists in the scope" do
284
+ doc = @document.new("name" => "joe", "first_scope" => "one", "second_scope" => "two")
285
+ doc.save.should be_true
286
+
287
+ @document \
288
+ .stubs(:first) \
289
+ .with(:name => 'joe', :first_scope => 'one', :second_scope => 'two') \
290
+ .returns(doc)
291
+
292
+ doc2 = @document.new("name" => "joe", "first_scope" => "one", "second_scope" => "two")
293
+ doc2.should have_error_on(:name)
294
+ end
295
+
296
+ should "pass if the same name exists in a different scope" do
297
+ doc = @document.new("name" => "joe", "first_scope" => "one", "second_scope" => "two")
298
+ doc.save.should be_true
299
+
300
+ @document \
301
+ .stubs(:first) \
302
+ .with(:name => 'joe', :first_scope => 'one', :second_scope => 'one') \
303
+ .returns(nil)
304
+
305
+ doc2 = @document.new("name" => "joe", "first_scope" => "one", "second_scope" => "one")
306
+ doc2.should_not have_error_on(:name)
307
+ end
308
+ end
309
+ end
310
+
311
+ context "validates uniqueness of with :unique shortcut" do
312
+ should "work" do
313
+ @document = Doc do
314
+ key :name, String, :unique => true
315
+ end
316
+
317
+ doc = @document.create(:name => 'John')
318
+ doc.should_not have_error_on(:name)
319
+
320
+ @document \
321
+ .stubs(:first) \
322
+ .with(:name => 'John') \
323
+ .returns(doc)
324
+
325
+ second_john = @document.create(:name => 'John')
326
+ second_john.should have_error_on(:name, 'has already been taken')
327
+ end
328
+ end
329
+ end