mongomodel 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 (108) hide show
  1. data/LICENSE +22 -0
  2. data/README.md +34 -0
  3. data/Rakefile +47 -0
  4. data/bin/console +45 -0
  5. data/lib/mongomodel.rb +92 -0
  6. data/lib/mongomodel/attributes/mongo.rb +40 -0
  7. data/lib/mongomodel/attributes/store.rb +30 -0
  8. data/lib/mongomodel/attributes/typecasting.rb +51 -0
  9. data/lib/mongomodel/concerns/abstract_class.rb +17 -0
  10. data/lib/mongomodel/concerns/activemodel.rb +11 -0
  11. data/lib/mongomodel/concerns/associations.rb +103 -0
  12. data/lib/mongomodel/concerns/associations/base/association.rb +33 -0
  13. data/lib/mongomodel/concerns/associations/base/definition.rb +56 -0
  14. data/lib/mongomodel/concerns/associations/base/proxy.rb +58 -0
  15. data/lib/mongomodel/concerns/associations/belongs_to.rb +68 -0
  16. data/lib/mongomodel/concerns/associations/has_many_by_foreign_key.rb +159 -0
  17. data/lib/mongomodel/concerns/associations/has_many_by_ids.rb +175 -0
  18. data/lib/mongomodel/concerns/attribute_methods.rb +55 -0
  19. data/lib/mongomodel/concerns/attribute_methods/before_type_cast.rb +29 -0
  20. data/lib/mongomodel/concerns/attribute_methods/dirty.rb +35 -0
  21. data/lib/mongomodel/concerns/attribute_methods/protected.rb +127 -0
  22. data/lib/mongomodel/concerns/attribute_methods/query.rb +22 -0
  23. data/lib/mongomodel/concerns/attribute_methods/read.rb +29 -0
  24. data/lib/mongomodel/concerns/attribute_methods/write.rb +29 -0
  25. data/lib/mongomodel/concerns/attributes.rb +85 -0
  26. data/lib/mongomodel/concerns/callbacks.rb +294 -0
  27. data/lib/mongomodel/concerns/logging.rb +15 -0
  28. data/lib/mongomodel/concerns/pretty_inspect.rb +29 -0
  29. data/lib/mongomodel/concerns/properties.rb +69 -0
  30. data/lib/mongomodel/concerns/record_status.rb +42 -0
  31. data/lib/mongomodel/concerns/timestamps.rb +32 -0
  32. data/lib/mongomodel/concerns/validations.rb +38 -0
  33. data/lib/mongomodel/concerns/validations/associated.rb +46 -0
  34. data/lib/mongomodel/document.rb +20 -0
  35. data/lib/mongomodel/document/callbacks.rb +46 -0
  36. data/lib/mongomodel/document/dynamic_finders.rb +88 -0
  37. data/lib/mongomodel/document/finders.rb +82 -0
  38. data/lib/mongomodel/document/indexes.rb +91 -0
  39. data/lib/mongomodel/document/optimistic_locking.rb +48 -0
  40. data/lib/mongomodel/document/persistence.rb +143 -0
  41. data/lib/mongomodel/document/scopes.rb +161 -0
  42. data/lib/mongomodel/document/validations.rb +68 -0
  43. data/lib/mongomodel/document/validations/uniqueness.rb +78 -0
  44. data/lib/mongomodel/embedded_document.rb +42 -0
  45. data/lib/mongomodel/locale/en.yml +55 -0
  46. data/lib/mongomodel/support/collection.rb +109 -0
  47. data/lib/mongomodel/support/configuration.rb +35 -0
  48. data/lib/mongomodel/support/core_extensions.rb +10 -0
  49. data/lib/mongomodel/support/exceptions.rb +25 -0
  50. data/lib/mongomodel/support/mongo_options.rb +177 -0
  51. data/lib/mongomodel/support/types.rb +35 -0
  52. data/lib/mongomodel/support/types/array.rb +11 -0
  53. data/lib/mongomodel/support/types/boolean.rb +25 -0
  54. data/lib/mongomodel/support/types/custom.rb +38 -0
  55. data/lib/mongomodel/support/types/date.rb +20 -0
  56. data/lib/mongomodel/support/types/float.rb +13 -0
  57. data/lib/mongomodel/support/types/hash.rb +18 -0
  58. data/lib/mongomodel/support/types/integer.rb +13 -0
  59. data/lib/mongomodel/support/types/object.rb +21 -0
  60. data/lib/mongomodel/support/types/string.rb +9 -0
  61. data/lib/mongomodel/support/types/symbol.rb +9 -0
  62. data/lib/mongomodel/support/types/time.rb +12 -0
  63. data/lib/mongomodel/version.rb +3 -0
  64. data/spec/mongomodel/attributes/store_spec.rb +273 -0
  65. data/spec/mongomodel/concerns/activemodel_spec.rb +61 -0
  66. data/spec/mongomodel/concerns/associations/belongs_to_spec.rb +153 -0
  67. data/spec/mongomodel/concerns/associations/has_many_by_foreign_key_spec.rb +165 -0
  68. data/spec/mongomodel/concerns/associations/has_many_by_ids_spec.rb +192 -0
  69. data/spec/mongomodel/concerns/attribute_methods/before_type_cast_spec.rb +46 -0
  70. data/spec/mongomodel/concerns/attribute_methods/dirty_spec.rb +131 -0
  71. data/spec/mongomodel/concerns/attribute_methods/protected_spec.rb +86 -0
  72. data/spec/mongomodel/concerns/attribute_methods/query_spec.rb +27 -0
  73. data/spec/mongomodel/concerns/attribute_methods/read_spec.rb +52 -0
  74. data/spec/mongomodel/concerns/attribute_methods/write_spec.rb +43 -0
  75. data/spec/mongomodel/concerns/attributes_spec.rb +152 -0
  76. data/spec/mongomodel/concerns/callbacks_spec.rb +90 -0
  77. data/spec/mongomodel/concerns/logging_spec.rb +20 -0
  78. data/spec/mongomodel/concerns/pretty_inspect_spec.rb +68 -0
  79. data/spec/mongomodel/concerns/properties_spec.rb +29 -0
  80. data/spec/mongomodel/concerns/timestamps_spec.rb +170 -0
  81. data/spec/mongomodel/concerns/validations_spec.rb +159 -0
  82. data/spec/mongomodel/document/callbacks_spec.rb +80 -0
  83. data/spec/mongomodel/document/dynamic_finders_spec.rb +183 -0
  84. data/spec/mongomodel/document/finders_spec.rb +231 -0
  85. data/spec/mongomodel/document/indexes_spec.rb +121 -0
  86. data/spec/mongomodel/document/optimistic_locking_spec.rb +57 -0
  87. data/spec/mongomodel/document/persistence_spec.rb +319 -0
  88. data/spec/mongomodel/document/scopes_spec.rb +204 -0
  89. data/spec/mongomodel/document/validations/uniqueness_spec.rb +217 -0
  90. data/spec/mongomodel/document/validations_spec.rb +132 -0
  91. data/spec/mongomodel/document_spec.rb +74 -0
  92. data/spec/mongomodel/embedded_document_spec.rb +66 -0
  93. data/spec/mongomodel/mongomodel_spec.rb +33 -0
  94. data/spec/mongomodel/support/collection_spec.rb +248 -0
  95. data/spec/mongomodel/support/mongo_options_spec.rb +295 -0
  96. data/spec/mongomodel/support/property_spec.rb +83 -0
  97. data/spec/spec.opts +6 -0
  98. data/spec/spec_helper.rb +21 -0
  99. data/spec/specdoc.opts +6 -0
  100. data/spec/support/callbacks.rb +44 -0
  101. data/spec/support/helpers/define_class.rb +24 -0
  102. data/spec/support/helpers/specs_for.rb +11 -0
  103. data/spec/support/matchers/be_a_subclass_of.rb +5 -0
  104. data/spec/support/matchers/respond_to_boolean.rb +17 -0
  105. data/spec/support/matchers/run_callbacks.rb +20 -0
  106. data/spec/support/models.rb +23 -0
  107. data/spec/support/time.rb +6 -0
  108. metadata +232 -0
@@ -0,0 +1,204 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ describe Document do
5
+ before(:all) do
6
+ class << MongoModel::Document
7
+ public :with_scope, :with_exclusive_scope
8
+
9
+ def should_find_with(hash)
10
+ should_receive(:_find).with(hash)
11
+ end
12
+ end
13
+ end
14
+
15
+ describe "#with_scope" do
16
+ define_class(:Article, Document) do
17
+ property :title, String
18
+ property :author, String
19
+ end
20
+
21
+ it "should deep merge finder options within block" do
22
+ Article.should_find_with({
23
+ :conditions => { :title => 'Hello World', :author => 'Editor' },
24
+ :limit => 10, :order => 'title ASC'
25
+ })
26
+
27
+ Article.with_scope(:find => { :conditions => { :title => 'Hello World' }, :limit => 10 }) do
28
+ Article.find(:all, :conditions => { :author => 'Editor' }, :order => 'title ASC')
29
+ end
30
+ end
31
+
32
+ it "should cascade multiple scopes" do
33
+ Article.should_find_with({
34
+ :conditions => { :title => 'Hello World', :author => 'Editor' },
35
+ :limit => 10, :order => 'title ASC', :select => :title
36
+ })
37
+
38
+ Article.with_scope(:find => { :conditions => { :title => 'Hello World' }}) do
39
+ Article.with_scope(:find => { :select => :title, :order => 'title ASC' }) do
40
+ Article.find(:all, :conditions => { :author => 'Editor' }, :limit => 10)
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ describe "#with_exclusive_scope" do
47
+ define_class(:Article, Document) do
48
+ property :title, String
49
+ property :author, String
50
+ end
51
+
52
+ it "should deep merge finder options within block" do
53
+ Article.should_find_with({
54
+ :conditions => { :title => 'Hello World', :author => 'Editor' },
55
+ :limit => 10, :order => 'title ASC'
56
+ })
57
+
58
+ Article.with_exclusive_scope(:find => { :conditions => { :title => 'Hello World' }, :limit => 10 }) do
59
+ Article.find(:all, :conditions => { :author => 'Editor' }, :order => 'title ASC')
60
+ end
61
+ end
62
+
63
+ it "should not cascade non-exclusive scopes" do
64
+ Article.should_find_with({
65
+ :conditions => { :author => 'Editor' },
66
+ :limit => 10, :order => 'title ASC', :select => :title
67
+ })
68
+
69
+ Article.with_scope(:find => { :conditions => { :title => 'Hello World' }}) do
70
+ Article.with_exclusive_scope(:find => { :select => :title, :order => 'title ASC' }) do
71
+ Article.find(:all, :conditions => { :author => 'Editor' }, :limit => 10)
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ describe "#default_scope" do
78
+ define_class(:User, Document) do
79
+ property :name, String
80
+ property :age, Integer
81
+
82
+ default_scope :conditions => { :age.gt => 18 }
83
+ end
84
+
85
+ it "should merge with other scopes" do
86
+ User.should_find_with(:conditions => { :age.gt => 18 })
87
+ User.find(:all)
88
+ end
89
+
90
+ it "should be overridable using #with_exclusive_scope" do
91
+ User.should_find_with({})
92
+ User.with_exclusive_scope({}) do
93
+ User.find(:all)
94
+ end
95
+ end
96
+ end
97
+
98
+ describe "#named_scope" do
99
+ define_class(:Post, Document) do
100
+ property :title, String
101
+ property :published, Boolean
102
+ property :created_at, Time
103
+
104
+ named_scope :published, :conditions => { :published => true }
105
+ named_scope :latest, lambda { |num| { :limit => num, :order => 'created_at DESC' } }
106
+ named_scope :exclusive, :exclusive => true
107
+ end
108
+
109
+ define_class(:SpecialPost, :Post)
110
+
111
+ it "should create scope methods" do
112
+ Post.published.options_for(:find).should == { :conditions => { :published => true} }
113
+ Post.latest(5).options_for(:find).should == { :limit => 5, :order => 'created_at DESC' }
114
+ Post.exclusive.options_for(:find).should == {}
115
+ end
116
+
117
+ it "should find using scope options" do
118
+ Post.should_find_with(:conditions => { :published => true })
119
+ Post.published.find(:all)
120
+
121
+ Post.should_find_with(:limit => 5, :order => 'created_at DESC')
122
+ Post.latest(5).find(:all)
123
+ end
124
+
125
+ it "should find by id using scope conditions" do
126
+ @post1 = Post.create(:id => 'post-1', :published => true)
127
+ @post2 = Post.create(:id => 'post-2', :published => false)
128
+
129
+ Post.published.find('post-1').should == @post1
130
+ lambda { Post.published.find('post-2') }.should raise_error(DocumentNotFound)
131
+ end
132
+
133
+ it "should count using scope options" do
134
+ 8.times { Post.create(:published => true) }
135
+ 3.times { Post.create(:published => false) }
136
+
137
+ Post.count.should == 11
138
+ Post.published.count.should == 8
139
+ end
140
+
141
+ it "should be chainable" do
142
+ Post.should_find_with(:conditions => { :published => true }, :limit => 5, :order => 'created_at DESC')
143
+ Post.published.latest(5).all
144
+ end
145
+
146
+ it "should inherit named scopes from parent classes" do
147
+ SpecialPost.published.options.should == Post.published.options
148
+ end
149
+
150
+ describe "an exclusive scope" do
151
+ it "should override non-exclusive scopes" do
152
+ Post.should_find_with({})
153
+ Post.published.exclusive.all
154
+ end
155
+ end
156
+
157
+ describe "#scoped" do
158
+ it "should create scopes on-the-fly" do
159
+ Post.should_find_with(:conditions => { :title => /^\d+/ })
160
+ Post.scoped(:conditions => { :title => /^\d+/ }).all
161
+ end
162
+ end
163
+ end
164
+ end
165
+
166
+ module DocumentExtensions
167
+ describe Scope do
168
+ before(:each) do
169
+ @model = mock('Model')
170
+ end
171
+
172
+ it "should be initializable with options" do
173
+ scope = Scope.new(@model, :find => { :conditions => { :foo => 'bar' } })
174
+ scope.options.should == { :find => { :conditions => { :foo => 'bar' } } }
175
+ end
176
+
177
+ it "should deep merge options to create new scopes" do
178
+ original = Scope.new(@model, :find => { :conditions => { :foo => 'bar' } })
179
+ merged = original.merge(:find => { :conditions => { :baz => 123 }, :limit => 5 })
180
+
181
+ original.options.should == { :find => { :conditions => { :foo => 'bar' } } }
182
+ merged.options.should == { :find => { :conditions => { :foo => 'bar', :baz => 123 }, :limit => 5 } }
183
+ end
184
+
185
+ it "should deep merge options to update an existing scope" do
186
+ scope = Scope.new(@model, :find => { :conditions => { :foo => 'bar' } })
187
+ merged = scope.merge!(:find => { :conditions => { :baz => 123 }, :limit => 5 })
188
+
189
+ merged.should == scope
190
+ scope.options.should == { :find => { :conditions => { :foo => 'bar', :baz => 123 }, :limit => 5 } }
191
+ end
192
+
193
+ it "should have find options" do
194
+ scope = Scope.new(@model, :find => { :conditions => { :foo => 'bar' } })
195
+ scope.options_for(:find).should == { :conditions => { :foo => 'bar' } }
196
+ end
197
+
198
+ it "should have default find options" do
199
+ scope = Scope.new(@model)
200
+ scope.options_for(:find).should == {}
201
+ end
202
+ end
203
+ end
204
+ end
@@ -0,0 +1,217 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ specs_for(Document) do
5
+ describe "validates_uniqueness_of" do
6
+ shared_examples_for "beating the race condition" do
7
+ before(:each) do
8
+ subject.stub!(:valid?).and_return(true, false)
9
+ end
10
+
11
+ describe "save" do
12
+ it "should return false" do
13
+ subject.save.should be_false
14
+ end
15
+
16
+ it "should add errors to the instance" do
17
+ subject.save
18
+ subject.errors[:title].should_not be_nil
19
+ end
20
+ end
21
+
22
+ describe "save!" do
23
+ it "should raise a DocumentInvalid exception" do
24
+ lambda { subject.save! }.should raise_error(DocumentInvalid)
25
+ end
26
+
27
+ it "should add errors to the instance" do
28
+ subject.save! rescue nil
29
+ subject.errors[:title].should_not be_nil
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "basic case" do
35
+ define_class(:Article, Document) do
36
+ property :title, String
37
+ validates_uniqueness_of :title
38
+ end
39
+
40
+ subject { Article.new(:title => 'Test') }
41
+
42
+ it "should be valid if no document with same title exists" do
43
+ subject.should be_valid
44
+ end
45
+
46
+ it "should not be valid if document with same title exists" do
47
+ Article.create!(:title => 'Test')
48
+ subject.should_not be_valid
49
+ end
50
+
51
+ it "should be valid if document with different-cased title exists" do
52
+ Article.create!(:title => 'TEST')
53
+ subject.should be_valid
54
+ end
55
+
56
+ it "should be valid if document already saved and no other document with same title exists" do
57
+ subject.save!
58
+ subject.should be_valid
59
+ end
60
+
61
+ describe "beating the race condition" do
62
+ before(:each) { Article.create!(:title => 'Test') }
63
+ it_should_behave_like "beating the race condition"
64
+ end
65
+ end
66
+
67
+ describe "with single scope" do
68
+ define_class(:Article, Document) do
69
+ property :title, String
70
+ property :category, String
71
+ validates_uniqueness_of :title, :scope => :category
72
+ end
73
+
74
+ before(:each) do
75
+ Article.create!(:title => 'Test', :category => 'Development')
76
+ end
77
+
78
+ subject { Article.new(:title => 'Test', :category => 'Testing') }
79
+
80
+ describe "no document with same title and category exists" do
81
+ it { should be_valid }
82
+ end
83
+
84
+ describe "document with same title and category exists" do
85
+ before(:each) { Article.create!(:title => 'Test', :category => 'Testing') }
86
+ it { should_not be_valid }
87
+ end
88
+
89
+ describe "document already saved" do
90
+ before(:each) { subject.save! }
91
+ it { should be_valid }
92
+ end
93
+
94
+ describe "beating the race condition" do
95
+ before(:each) { Article.create!(:title => 'Test', :category => 'Testing') }
96
+ it_should_behave_like "beating the race condition"
97
+ end
98
+ end
99
+
100
+ describe "with array scope" do
101
+ define_class(:Article, Document) do
102
+ property :title, String
103
+ property :category, String
104
+ property :year, Integer
105
+ validates_uniqueness_of :title, :scope => [:category, :year]
106
+ end
107
+
108
+ before(:each) do
109
+ Article.create!(:title => 'Test', :category => 'Development', :year => 2008)
110
+ end
111
+
112
+ subject { Article.new(:title => 'Test', :category => 'Testing', :year => 2009) }
113
+
114
+ describe "no document with same title and category exists" do
115
+ it { should be_valid }
116
+ end
117
+
118
+ describe "document with same title and category exists" do
119
+ before(:each) { Article.create!(:title => 'Test', :category => 'Testing', :year => 2009) }
120
+ it { should_not be_valid }
121
+ end
122
+
123
+ describe "document already saved" do
124
+ before(:each) { subject.save! }
125
+ it { should be_valid }
126
+ end
127
+
128
+ describe "beating the race condition" do
129
+ before(:each) { Article.create!(:title => 'Test', :category => 'Testing', :year => 2009) }
130
+ it_should_behave_like "beating the race condition"
131
+ end
132
+ end
133
+
134
+ describe "case insensitive" do
135
+ define_class(:Article, Document) do
136
+ property :title, String
137
+ validates_uniqueness_of :title, :case_sensitive => false
138
+ end
139
+
140
+ subject { Article.new(:title => 'Test') }
141
+
142
+ describe "no document with same title exists" do
143
+ it { should be_valid }
144
+ end
145
+
146
+ describe "document with same title exists" do
147
+ before(:each) { Article.create!(:title => 'Test') }
148
+ it { should_not be_valid }
149
+ end
150
+
151
+ describe "document with different-cased title exists" do
152
+ before(:each) { Article.create!(:title => 'TEST') }
153
+ it { should_not be_valid }
154
+ end
155
+
156
+ describe "document already saved" do
157
+ before(:each) { subject.save! }
158
+ it { should be_valid }
159
+ end
160
+
161
+ describe "beating the race condition" do
162
+ before(:each) { Article.create!(:title => 'TEST') }
163
+ it_should_behave_like "beating the race condition"
164
+ end
165
+ end
166
+
167
+ describe "validation on parent class" do
168
+ define_class(:Article, Document) do
169
+ property :title, String
170
+ validates_uniqueness_of :title
171
+ end
172
+ define_class(:SpecialArticle, :Article)
173
+
174
+ subject { SpecialArticle.new(:title => 'Test') }
175
+
176
+ describe "no document with same title exists" do
177
+ it { should be_valid }
178
+ end
179
+
180
+ describe "parent document with same title exists" do
181
+ before(:each) { Article.create!(:title => 'Test') }
182
+ it { should_not be_valid }
183
+ end
184
+
185
+ describe "child document with same title exists" do
186
+ before(:each) { SpecialArticle.create!(:title => 'Test') }
187
+ it { should_not be_valid }
188
+ end
189
+ end
190
+
191
+ describe "validation on child class" do
192
+ define_class(:Article, Document) do
193
+ property :title, String
194
+ end
195
+ define_class(:SpecialArticle, :Article) do
196
+ validates_uniqueness_of :title
197
+ end
198
+
199
+ subject { SpecialArticle.new(:title => 'Test') }
200
+
201
+ describe "no document with same title exists" do
202
+ it { should be_valid }
203
+ end
204
+
205
+ describe "parent document with same title exists" do
206
+ before(:each) { Article.create!(:title => 'Test') }
207
+ it { should be_valid }
208
+ end
209
+
210
+ describe "child document with same title exists" do
211
+ before(:each) { SpecialArticle.create!(:title => 'Test') }
212
+ it { should_not be_valid }
213
+ end
214
+ end
215
+ end
216
+ end
217
+ end
@@ -0,0 +1,132 @@
1
+ require 'spec_helper'
2
+
3
+ module MongoModel
4
+ specs_for(Document) do
5
+ describe "validations" do
6
+ define_class(:TestDocument, Document) do
7
+ property :title, String
8
+ validates_presence_of :title
9
+ end
10
+
11
+ context "when validations are not met" do
12
+ subject { TestDocument.new }
13
+
14
+ describe "#save" do
15
+ it "should return false" do
16
+ subject.save.should be_false
17
+ end
18
+ end
19
+
20
+ describe "#save!" do
21
+ before(:each) do
22
+ subject.errors.stub!(:full_messages).and_return(["first error", "second error"])
23
+ end
24
+
25
+ it "should raise a MongoModel::DocumentInvalid exception" do
26
+ lambda { subject.save! }.should raise_error(MongoModel::DocumentInvalid, "Validation failed: first error, second error")
27
+ end
28
+ end
29
+
30
+ describe "#save(false)" do
31
+ it "should not validate the document" do
32
+ subject.should_not_receive(:valid?)
33
+ subject.save(false)
34
+ end
35
+
36
+ it "should save the document" do
37
+ subject.should_receive(:save_without_validation).and_return(true)
38
+ subject.save(false)
39
+ end
40
+
41
+ it "should return true" do
42
+ subject.save(false).should be_true
43
+ end
44
+ end
45
+ end
46
+
47
+ describe "#create!" do
48
+ define_class(:User, Document) do
49
+ property :name, String
50
+ property :age, Integer
51
+
52
+ validates_presence_of :name
53
+ end
54
+
55
+ context "attributes hash" do
56
+ it "should pass attributes to instance" do
57
+ @user = User.create!(:name => 'Test', :age => 18)
58
+ @user.name.should == 'Test'
59
+ @user.age.should == 18
60
+ end
61
+
62
+ it "should save! the instance" do
63
+ User.create!(:name => 'Test').should_not be_a_new_record
64
+ end
65
+
66
+ it "should yield the instance to a given block before saving" do
67
+ block_called = false
68
+
69
+ User.create!(:name => 'Test') do |u|
70
+ block_called = true
71
+
72
+ u.should be_an_instance_of(User)
73
+ u.should be_a_new_record
74
+ end
75
+
76
+ block_called.should be_true
77
+ end
78
+
79
+ it "should raise an exception if the document is invalid" do
80
+ lambda { User.create! }.should raise_error(DocumentInvalid)
81
+ end
82
+ end
83
+
84
+ context "array of attribute hashes" do
85
+ def create_users(&block)
86
+ User.create!([{ :name => 'Test', :age => 18 }, { :name => 'Second', :age => 21 }], &block)
87
+ end
88
+
89
+ it "should return instances in array with associated attributes" do
90
+ @users = create_users
91
+ @users[0].name.should == 'Test'
92
+ @users[0].age.should == 18
93
+ @users[1].name.should == 'Second'
94
+ @users[1].age.should == 21
95
+ end
96
+
97
+ it "should save! each instance" do
98
+ create_users.each { |user| user.should_not be_a_new_record }
99
+ end
100
+
101
+ it "should yield each instance to a given block before saving" do
102
+ block_called = 0
103
+
104
+ create_users do |u|
105
+ block_called += 1
106
+
107
+ u.should be_an_instance_of(User)
108
+ u.should be_a_new_record
109
+ end
110
+
111
+ block_called.should == 2
112
+ end
113
+
114
+ it "should raise an exception if a document is invalid" do
115
+ lambda { User.create!([ {}, {} ]) }.should raise_error(DocumentInvalid)
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ describe "validation shortcuts" do
122
+ define_class(:TestDocument, Document)
123
+
124
+ describe ":unique => true" do
125
+ it "should add a validates_uniqueness_of validation" do
126
+ TestDocument.should_receive(:validates_uniqueness_of).with(:title)
127
+ TestDocument.property :title, String, :unique => true
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end