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.
- data/LICENSE +22 -0
- data/README.md +34 -0
- data/Rakefile +47 -0
- data/bin/console +45 -0
- data/lib/mongomodel.rb +92 -0
- data/lib/mongomodel/attributes/mongo.rb +40 -0
- data/lib/mongomodel/attributes/store.rb +30 -0
- data/lib/mongomodel/attributes/typecasting.rb +51 -0
- data/lib/mongomodel/concerns/abstract_class.rb +17 -0
- data/lib/mongomodel/concerns/activemodel.rb +11 -0
- data/lib/mongomodel/concerns/associations.rb +103 -0
- data/lib/mongomodel/concerns/associations/base/association.rb +33 -0
- data/lib/mongomodel/concerns/associations/base/definition.rb +56 -0
- data/lib/mongomodel/concerns/associations/base/proxy.rb +58 -0
- data/lib/mongomodel/concerns/associations/belongs_to.rb +68 -0
- data/lib/mongomodel/concerns/associations/has_many_by_foreign_key.rb +159 -0
- data/lib/mongomodel/concerns/associations/has_many_by_ids.rb +175 -0
- data/lib/mongomodel/concerns/attribute_methods.rb +55 -0
- data/lib/mongomodel/concerns/attribute_methods/before_type_cast.rb +29 -0
- data/lib/mongomodel/concerns/attribute_methods/dirty.rb +35 -0
- data/lib/mongomodel/concerns/attribute_methods/protected.rb +127 -0
- data/lib/mongomodel/concerns/attribute_methods/query.rb +22 -0
- data/lib/mongomodel/concerns/attribute_methods/read.rb +29 -0
- data/lib/mongomodel/concerns/attribute_methods/write.rb +29 -0
- data/lib/mongomodel/concerns/attributes.rb +85 -0
- data/lib/mongomodel/concerns/callbacks.rb +294 -0
- data/lib/mongomodel/concerns/logging.rb +15 -0
- data/lib/mongomodel/concerns/pretty_inspect.rb +29 -0
- data/lib/mongomodel/concerns/properties.rb +69 -0
- data/lib/mongomodel/concerns/record_status.rb +42 -0
- data/lib/mongomodel/concerns/timestamps.rb +32 -0
- data/lib/mongomodel/concerns/validations.rb +38 -0
- data/lib/mongomodel/concerns/validations/associated.rb +46 -0
- data/lib/mongomodel/document.rb +20 -0
- data/lib/mongomodel/document/callbacks.rb +46 -0
- data/lib/mongomodel/document/dynamic_finders.rb +88 -0
- data/lib/mongomodel/document/finders.rb +82 -0
- data/lib/mongomodel/document/indexes.rb +91 -0
- data/lib/mongomodel/document/optimistic_locking.rb +48 -0
- data/lib/mongomodel/document/persistence.rb +143 -0
- data/lib/mongomodel/document/scopes.rb +161 -0
- data/lib/mongomodel/document/validations.rb +68 -0
- data/lib/mongomodel/document/validations/uniqueness.rb +78 -0
- data/lib/mongomodel/embedded_document.rb +42 -0
- data/lib/mongomodel/locale/en.yml +55 -0
- data/lib/mongomodel/support/collection.rb +109 -0
- data/lib/mongomodel/support/configuration.rb +35 -0
- data/lib/mongomodel/support/core_extensions.rb +10 -0
- data/lib/mongomodel/support/exceptions.rb +25 -0
- data/lib/mongomodel/support/mongo_options.rb +177 -0
- data/lib/mongomodel/support/types.rb +35 -0
- data/lib/mongomodel/support/types/array.rb +11 -0
- data/lib/mongomodel/support/types/boolean.rb +25 -0
- data/lib/mongomodel/support/types/custom.rb +38 -0
- data/lib/mongomodel/support/types/date.rb +20 -0
- data/lib/mongomodel/support/types/float.rb +13 -0
- data/lib/mongomodel/support/types/hash.rb +18 -0
- data/lib/mongomodel/support/types/integer.rb +13 -0
- data/lib/mongomodel/support/types/object.rb +21 -0
- data/lib/mongomodel/support/types/string.rb +9 -0
- data/lib/mongomodel/support/types/symbol.rb +9 -0
- data/lib/mongomodel/support/types/time.rb +12 -0
- data/lib/mongomodel/version.rb +3 -0
- data/spec/mongomodel/attributes/store_spec.rb +273 -0
- data/spec/mongomodel/concerns/activemodel_spec.rb +61 -0
- data/spec/mongomodel/concerns/associations/belongs_to_spec.rb +153 -0
- data/spec/mongomodel/concerns/associations/has_many_by_foreign_key_spec.rb +165 -0
- data/spec/mongomodel/concerns/associations/has_many_by_ids_spec.rb +192 -0
- data/spec/mongomodel/concerns/attribute_methods/before_type_cast_spec.rb +46 -0
- data/spec/mongomodel/concerns/attribute_methods/dirty_spec.rb +131 -0
- data/spec/mongomodel/concerns/attribute_methods/protected_spec.rb +86 -0
- data/spec/mongomodel/concerns/attribute_methods/query_spec.rb +27 -0
- data/spec/mongomodel/concerns/attribute_methods/read_spec.rb +52 -0
- data/spec/mongomodel/concerns/attribute_methods/write_spec.rb +43 -0
- data/spec/mongomodel/concerns/attributes_spec.rb +152 -0
- data/spec/mongomodel/concerns/callbacks_spec.rb +90 -0
- data/spec/mongomodel/concerns/logging_spec.rb +20 -0
- data/spec/mongomodel/concerns/pretty_inspect_spec.rb +68 -0
- data/spec/mongomodel/concerns/properties_spec.rb +29 -0
- data/spec/mongomodel/concerns/timestamps_spec.rb +170 -0
- data/spec/mongomodel/concerns/validations_spec.rb +159 -0
- data/spec/mongomodel/document/callbacks_spec.rb +80 -0
- data/spec/mongomodel/document/dynamic_finders_spec.rb +183 -0
- data/spec/mongomodel/document/finders_spec.rb +231 -0
- data/spec/mongomodel/document/indexes_spec.rb +121 -0
- data/spec/mongomodel/document/optimistic_locking_spec.rb +57 -0
- data/spec/mongomodel/document/persistence_spec.rb +319 -0
- data/spec/mongomodel/document/scopes_spec.rb +204 -0
- data/spec/mongomodel/document/validations/uniqueness_spec.rb +217 -0
- data/spec/mongomodel/document/validations_spec.rb +132 -0
- data/spec/mongomodel/document_spec.rb +74 -0
- data/spec/mongomodel/embedded_document_spec.rb +66 -0
- data/spec/mongomodel/mongomodel_spec.rb +33 -0
- data/spec/mongomodel/support/collection_spec.rb +248 -0
- data/spec/mongomodel/support/mongo_options_spec.rb +295 -0
- data/spec/mongomodel/support/property_spec.rb +83 -0
- data/spec/spec.opts +6 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/specdoc.opts +6 -0
- data/spec/support/callbacks.rb +44 -0
- data/spec/support/helpers/define_class.rb +24 -0
- data/spec/support/helpers/specs_for.rb +11 -0
- data/spec/support/matchers/be_a_subclass_of.rb +5 -0
- data/spec/support/matchers/respond_to_boolean.rb +17 -0
- data/spec/support/matchers/run_callbacks.rb +20 -0
- data/spec/support/models.rb +23 -0
- data/spec/support/time.rb +6 -0
- 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
|