couchrest_model 1.1.0.beta → 1.1.0.beta2
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/Gemfile.lock +15 -15
- data/README.md +42 -0
- data/VERSION +1 -1
- data/couchrest_model.gemspec +1 -1
- data/history.txt +9 -0
- data/lib/couchrest/model/associations.rb +62 -50
- data/lib/couchrest/model/casted_model.rb +1 -1
- data/lib/couchrest/model/{support → core_extensions}/hash.rb +0 -0
- data/lib/couchrest/model/core_extensions/time_parsing.rb +66 -0
- data/lib/couchrest/model/designs/view.rb +34 -14
- data/lib/couchrest/model/properties.rb +4 -2
- data/lib/couchrest/model/proxyable.rb +18 -13
- data/lib/couchrest/model/typecast.rb +8 -28
- data/lib/couchrest/model/validations/uniqueness.rb +20 -9
- data/lib/couchrest/model/views.rb +1 -1
- data/lib/couchrest_model.rb +3 -1
- data/spec/couchrest/assocations_spec.rb +31 -10
- data/spec/couchrest/core_extensions/time_parsing.rb +77 -0
- data/spec/couchrest/designs/view_spec.rb +60 -19
- data/spec/couchrest/property_spec.rb +1 -505
- data/spec/couchrest/proxyable_spec.rb +46 -27
- data/spec/couchrest/typecast_spec.rb +524 -0
- data/spec/couchrest/validations_spec.rb +84 -36
- data/spec/fixtures/base.rb +9 -0
- metadata +10 -6
@@ -0,0 +1,524 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
3
|
+
require File.join(FIXTURE_PATH, 'more', 'cat')
|
4
|
+
require File.join(FIXTURE_PATH, 'more', 'person')
|
5
|
+
require File.join(FIXTURE_PATH, 'more', 'course')
|
6
|
+
|
7
|
+
describe "Type Casting" do
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
@course = Course.new(:title => 'Relaxation')
|
11
|
+
end
|
12
|
+
|
13
|
+
describe "when value is nil" do
|
14
|
+
it "leaves the value unchanged" do
|
15
|
+
@course.title = nil
|
16
|
+
@course['title'].should == nil
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "when type primitive is an Object" do
|
21
|
+
it "it should not cast given value" do
|
22
|
+
@course.participants = [{}, 'q', 1]
|
23
|
+
@course['participants'].should == [{}, 'q', 1]
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should cast started_on to Date" do
|
27
|
+
@course.started_on = Date.today
|
28
|
+
@course['started_on'].should be_an_instance_of(Date)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
describe "when type primitive is a String" do
|
33
|
+
it "keeps string value unchanged" do
|
34
|
+
value = "1.0"
|
35
|
+
@course.title = value
|
36
|
+
@course['title'].should equal(value)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "it casts to string representation of the value" do
|
40
|
+
@course.title = 1.0
|
41
|
+
@course['title'].should eql("1.0")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
describe 'when type primitive is a Float' do
|
46
|
+
it 'returns same value if a float' do
|
47
|
+
value = 24.0
|
48
|
+
@course.estimate = value
|
49
|
+
@course['estimate'].should equal(value)
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'returns float representation of a zero string integer' do
|
53
|
+
@course.estimate = '0'
|
54
|
+
@course['estimate'].should eql(0.0)
|
55
|
+
end
|
56
|
+
|
57
|
+
it 'returns float representation of a positive string integer' do
|
58
|
+
@course.estimate = '24'
|
59
|
+
@course['estimate'].should eql(24.0)
|
60
|
+
end
|
61
|
+
|
62
|
+
it 'returns float representation of a negative string integer' do
|
63
|
+
@course.estimate = '-24'
|
64
|
+
@course['estimate'].should eql(-24.0)
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'returns float representation of a zero string float' do
|
68
|
+
@course.estimate = '0.0'
|
69
|
+
@course['estimate'].should eql(0.0)
|
70
|
+
end
|
71
|
+
|
72
|
+
it 'returns float representation of a positive string float' do
|
73
|
+
@course.estimate = '24.35'
|
74
|
+
@course['estimate'].should eql(24.35)
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'returns float representation of a negative string float' do
|
78
|
+
@course.estimate = '-24.35'
|
79
|
+
@course['estimate'].should eql(-24.35)
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'returns float representation of a zero string float, with no leading digits' do
|
83
|
+
@course.estimate = '.0'
|
84
|
+
@course['estimate'].should eql(0.0)
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'returns float representation of a positive string float, with no leading digits' do
|
88
|
+
@course.estimate = '.41'
|
89
|
+
@course['estimate'].should eql(0.41)
|
90
|
+
end
|
91
|
+
|
92
|
+
it 'returns float representation of a zero integer' do
|
93
|
+
@course.estimate = 0
|
94
|
+
@course['estimate'].should eql(0.0)
|
95
|
+
end
|
96
|
+
|
97
|
+
it 'returns float representation of a positive integer' do
|
98
|
+
@course.estimate = 24
|
99
|
+
@course['estimate'].should eql(24.0)
|
100
|
+
end
|
101
|
+
|
102
|
+
it 'returns float representation of a negative integer' do
|
103
|
+
@course.estimate = -24
|
104
|
+
@course['estimate'].should eql(-24.0)
|
105
|
+
end
|
106
|
+
|
107
|
+
it 'returns float representation of a zero decimal' do
|
108
|
+
@course.estimate = BigDecimal('0.0')
|
109
|
+
@course['estimate'].should eql(0.0)
|
110
|
+
end
|
111
|
+
|
112
|
+
it 'returns float representation of a positive decimal' do
|
113
|
+
@course.estimate = BigDecimal('24.35')
|
114
|
+
@course['estimate'].should eql(24.35)
|
115
|
+
end
|
116
|
+
|
117
|
+
it 'returns float representation of a negative decimal' do
|
118
|
+
@course.estimate = BigDecimal('-24.35')
|
119
|
+
@course['estimate'].should eql(-24.35)
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'return float of a number with commas instead of points for decimals' do
|
123
|
+
@course.estimate = '23,35'
|
124
|
+
@course['estimate'].should eql(23.35)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should handle numbers with commas and points" do
|
128
|
+
@course.estimate = '1,234.00'
|
129
|
+
@course.estimate.should eql(1234.00)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should handle a mis-match of commas and points and maintain the last one" do
|
133
|
+
@course.estimate = "1,232.434.123,323"
|
134
|
+
@course.estimate.should eql(1232434123.323)
|
135
|
+
end
|
136
|
+
|
137
|
+
it "should handle numbers with whitespace" do
|
138
|
+
@course.estimate = " 24.35 "
|
139
|
+
@course.estimate.should eql(24.35)
|
140
|
+
end
|
141
|
+
|
142
|
+
[ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
|
143
|
+
it "does not typecast non-numeric value #{value.inspect}" do
|
144
|
+
@course.estimate = value
|
145
|
+
@course['estimate'].should equal(value)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
describe 'when type primitive is a Integer' do
|
152
|
+
it 'returns same value if an integer' do
|
153
|
+
value = 24
|
154
|
+
@course.hours = value
|
155
|
+
@course['hours'].should equal(value)
|
156
|
+
end
|
157
|
+
|
158
|
+
it 'returns integer representation of a zero string integer' do
|
159
|
+
@course.hours = '0'
|
160
|
+
@course['hours'].should eql(0)
|
161
|
+
end
|
162
|
+
|
163
|
+
it 'returns integer representation of a positive string integer' do
|
164
|
+
@course.hours = '24'
|
165
|
+
@course['hours'].should eql(24)
|
166
|
+
end
|
167
|
+
|
168
|
+
it 'returns integer representation of a negative string integer' do
|
169
|
+
@course.hours = '-24'
|
170
|
+
@course['hours'].should eql(-24)
|
171
|
+
end
|
172
|
+
|
173
|
+
it 'returns integer representation of a zero string float' do
|
174
|
+
@course.hours = '0.0'
|
175
|
+
@course['hours'].should eql(0)
|
176
|
+
end
|
177
|
+
|
178
|
+
it 'returns integer representation of a positive string float' do
|
179
|
+
@course.hours = '24.35'
|
180
|
+
@course['hours'].should eql(24)
|
181
|
+
end
|
182
|
+
|
183
|
+
it 'returns integer representation of a negative string float' do
|
184
|
+
@course.hours = '-24.35'
|
185
|
+
@course['hours'].should eql(-24)
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'returns integer representation of a zero string float, with no leading digits' do
|
189
|
+
@course.hours = '.0'
|
190
|
+
@course['hours'].should eql(0)
|
191
|
+
end
|
192
|
+
|
193
|
+
it 'returns integer representation of a positive string float, with no leading digits' do
|
194
|
+
@course.hours = '.41'
|
195
|
+
@course['hours'].should eql(0)
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'returns integer representation of a zero float' do
|
199
|
+
@course.hours = 0.0
|
200
|
+
@course['hours'].should eql(0)
|
201
|
+
end
|
202
|
+
|
203
|
+
it 'returns integer representation of a positive float' do
|
204
|
+
@course.hours = 24.35
|
205
|
+
@course['hours'].should eql(24)
|
206
|
+
end
|
207
|
+
|
208
|
+
it 'returns integer representation of a negative float' do
|
209
|
+
@course.hours = -24.35
|
210
|
+
@course['hours'].should eql(-24)
|
211
|
+
end
|
212
|
+
|
213
|
+
it 'returns integer representation of a zero decimal' do
|
214
|
+
@course.hours = '0.0'
|
215
|
+
@course['hours'].should eql(0)
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'returns integer representation of a positive decimal' do
|
219
|
+
@course.hours = '24.35'
|
220
|
+
@course['hours'].should eql(24)
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'returns integer representation of a negative decimal' do
|
224
|
+
@course.hours = '-24.35'
|
225
|
+
@course['hours'].should eql(-24)
|
226
|
+
end
|
227
|
+
|
228
|
+
it "should handle numbers with whitespace" do
|
229
|
+
@course.hours = " 24 "
|
230
|
+
@course['hours'].should eql(24)
|
231
|
+
end
|
232
|
+
|
233
|
+
[ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
|
234
|
+
it "does not typecast non-numeric value #{value.inspect}" do
|
235
|
+
@course.hours = value
|
236
|
+
@course['hours'].should equal(value)
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
describe 'when type primitive is a BigDecimal' do
|
242
|
+
it 'returns same value if a decimal' do
|
243
|
+
value = BigDecimal('24.0')
|
244
|
+
@course.profit = value
|
245
|
+
@course['profit'].should equal(value)
|
246
|
+
end
|
247
|
+
|
248
|
+
it 'returns decimal representation of a zero string integer' do
|
249
|
+
@course.profit = '0'
|
250
|
+
@course['profit'].should eql(BigDecimal('0.0'))
|
251
|
+
end
|
252
|
+
|
253
|
+
it 'returns decimal representation of a positive string integer' do
|
254
|
+
@course.profit = '24'
|
255
|
+
@course['profit'].should eql(BigDecimal('24.0'))
|
256
|
+
end
|
257
|
+
|
258
|
+
it 'returns decimal representation of a negative string integer' do
|
259
|
+
@course.profit = '-24'
|
260
|
+
@course['profit'].should eql(BigDecimal('-24.0'))
|
261
|
+
end
|
262
|
+
|
263
|
+
it 'returns decimal representation of a zero string float' do
|
264
|
+
@course.profit = '0.0'
|
265
|
+
@course['profit'].should eql(BigDecimal('0.0'))
|
266
|
+
end
|
267
|
+
|
268
|
+
it 'returns decimal representation of a positive string float' do
|
269
|
+
@course.profit = '24.35'
|
270
|
+
@course['profit'].should eql(BigDecimal('24.35'))
|
271
|
+
end
|
272
|
+
|
273
|
+
it 'returns decimal representation of a negative string float' do
|
274
|
+
@course.profit = '-24.35'
|
275
|
+
@course['profit'].should eql(BigDecimal('-24.35'))
|
276
|
+
end
|
277
|
+
|
278
|
+
it 'returns decimal representation of a zero string float, with no leading digits' do
|
279
|
+
@course.profit = '.0'
|
280
|
+
@course['profit'].should eql(BigDecimal('0.0'))
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'returns decimal representation of a positive string float, with no leading digits' do
|
284
|
+
@course.profit = '.41'
|
285
|
+
@course['profit'].should eql(BigDecimal('0.41'))
|
286
|
+
end
|
287
|
+
|
288
|
+
it 'returns decimal representation of a zero integer' do
|
289
|
+
@course.profit = 0
|
290
|
+
@course['profit'].should eql(BigDecimal('0.0'))
|
291
|
+
end
|
292
|
+
|
293
|
+
it 'returns decimal representation of a positive integer' do
|
294
|
+
@course.profit = 24
|
295
|
+
@course['profit'].should eql(BigDecimal('24.0'))
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'returns decimal representation of a negative integer' do
|
299
|
+
@course.profit = -24
|
300
|
+
@course['profit'].should eql(BigDecimal('-24.0'))
|
301
|
+
end
|
302
|
+
|
303
|
+
it 'returns decimal representation of a zero float' do
|
304
|
+
@course.profit = 0.0
|
305
|
+
@course['profit'].should eql(BigDecimal('0.0'))
|
306
|
+
end
|
307
|
+
|
308
|
+
it 'returns decimal representation of a positive float' do
|
309
|
+
@course.profit = 24.35
|
310
|
+
@course['profit'].should eql(BigDecimal('24.35'))
|
311
|
+
end
|
312
|
+
|
313
|
+
it 'returns decimal representation of a negative float' do
|
314
|
+
@course.profit = -24.35
|
315
|
+
@course['profit'].should eql(BigDecimal('-24.35'))
|
316
|
+
end
|
317
|
+
|
318
|
+
it "should handle numbers with whitespace" do
|
319
|
+
@course.profit = " 24.35 "
|
320
|
+
@course['profit'].should eql(BigDecimal('24.35'))
|
321
|
+
end
|
322
|
+
|
323
|
+
[ Object.new, true, '00.0', '0.', '-.0', 'string' ].each do |value|
|
324
|
+
it "does not typecast non-numeric value #{value.inspect}" do
|
325
|
+
@course.profit = value
|
326
|
+
@course['profit'].should equal(value)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
end
|
330
|
+
|
331
|
+
describe 'when type primitive is a DateTime' do
|
332
|
+
describe 'and value given as a hash with keys like :year, :month, etc' do
|
333
|
+
it 'builds a DateTime instance from hash values' do
|
334
|
+
@course.updated_at = {
|
335
|
+
:year => '2006',
|
336
|
+
:month => '11',
|
337
|
+
:day => '23',
|
338
|
+
:hour => '12',
|
339
|
+
:min => '0',
|
340
|
+
:sec => '0'
|
341
|
+
}
|
342
|
+
result = @course['updated_at']
|
343
|
+
|
344
|
+
result.should be_kind_of(DateTime)
|
345
|
+
result.year.should eql(2006)
|
346
|
+
result.month.should eql(11)
|
347
|
+
result.day.should eql(23)
|
348
|
+
result.hour.should eql(12)
|
349
|
+
result.min.should eql(0)
|
350
|
+
result.sec.should eql(0)
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
describe 'and value is a string' do
|
355
|
+
it 'parses the string' do
|
356
|
+
@course.updated_at = 'Dec, 2006'
|
357
|
+
@course['updated_at'].month.should == 12
|
358
|
+
end
|
359
|
+
end
|
360
|
+
|
361
|
+
it 'does not typecast non-datetime values' do
|
362
|
+
@course.updated_at = 'not-datetime'
|
363
|
+
@course['updated_at'].should eql('not-datetime')
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
describe 'when type primitive is a Date' do
|
368
|
+
describe 'and value given as a hash with keys like :year, :month, etc' do
|
369
|
+
it 'builds a Date instance from hash values' do
|
370
|
+
@course.started_on = {
|
371
|
+
:year => '2007',
|
372
|
+
:month => '3',
|
373
|
+
:day => '25'
|
374
|
+
}
|
375
|
+
result = @course['started_on']
|
376
|
+
|
377
|
+
result.should be_kind_of(Date)
|
378
|
+
result.year.should eql(2007)
|
379
|
+
result.month.should eql(3)
|
380
|
+
result.day.should eql(25)
|
381
|
+
end
|
382
|
+
end
|
383
|
+
|
384
|
+
describe 'and value is a string' do
|
385
|
+
it 'parses the string' do
|
386
|
+
@course.started_on = 'Dec 20th, 2006'
|
387
|
+
@course.started_on.month.should == 12
|
388
|
+
@course.started_on.day.should == 20
|
389
|
+
@course.started_on.year.should == 2006
|
390
|
+
end
|
391
|
+
end
|
392
|
+
|
393
|
+
it 'does not typecast non-date values' do
|
394
|
+
@course.started_on = 'not-date'
|
395
|
+
@course['started_on'].should eql('not-date')
|
396
|
+
end
|
397
|
+
end
|
398
|
+
|
399
|
+
describe 'when type primitive is a Time' do
|
400
|
+
describe 'and value given as a hash with keys like :year, :month, etc' do
|
401
|
+
it 'builds a Time instance from hash values' do
|
402
|
+
@course.ends_at = {
|
403
|
+
:year => '2006',
|
404
|
+
:month => '11',
|
405
|
+
:day => '23',
|
406
|
+
:hour => '12',
|
407
|
+
:min => '0',
|
408
|
+
:sec => '0'
|
409
|
+
}
|
410
|
+
result = @course['ends_at']
|
411
|
+
|
412
|
+
result.should be_kind_of(Time)
|
413
|
+
result.year.should eql(2006)
|
414
|
+
result.month.should eql(11)
|
415
|
+
result.day.should eql(23)
|
416
|
+
result.hour.should eql(12)
|
417
|
+
result.min.should eql(0)
|
418
|
+
result.sec.should eql(0)
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
describe 'and value is a string' do
|
423
|
+
it 'parses the string' do
|
424
|
+
t = Time.new(2011, 4, 1, 18, 50, 32, "+02:00")
|
425
|
+
@course.ends_at = t.strftime('%Y/%m/%d %H:%M:%S %z')
|
426
|
+
@course['ends_at'].year.should eql(t.year)
|
427
|
+
@course['ends_at'].month.should eql(t.month)
|
428
|
+
@course['ends_at'].day.should eql(t.day)
|
429
|
+
@course['ends_at'].hour.should eql(t.hour)
|
430
|
+
@course['ends_at'].min.should eql(t.min)
|
431
|
+
@course['ends_at'].sec.should eql(t.sec)
|
432
|
+
end
|
433
|
+
it 'parses the string without offset as UTC' do
|
434
|
+
t = Time.now.utc
|
435
|
+
@course.ends_at = t.strftime("%Y-%m-%d %H:%M:%S")
|
436
|
+
@course.ends_at.utc?.should be_true
|
437
|
+
@course['ends_at'].year.should eql(t.year)
|
438
|
+
@course['ends_at'].month.should eql(t.month)
|
439
|
+
@course['ends_at'].day.should eql(t.day)
|
440
|
+
@course['ends_at'].hour.should eql(t.hour)
|
441
|
+
@course['ends_at'].min.should eql(t.min)
|
442
|
+
@course['ends_at'].sec.should eql(t.sec)
|
443
|
+
end
|
444
|
+
end
|
445
|
+
|
446
|
+
it "converts a time value into utc" do
|
447
|
+
t = Time.new(2011, 4, 1, 18, 50, 32, "+02:00")
|
448
|
+
@course.ends_at = t
|
449
|
+
@course.ends_at.utc?.should be_true
|
450
|
+
@course.ends_at.to_i.should eql(Time.utc(2011, 4, 1, 16, 50, 32).to_i)
|
451
|
+
end
|
452
|
+
|
453
|
+
if RUBY_VERSION >= "1.9.1"
|
454
|
+
# In ruby 1.8.7 Time.parse will always return a value. D'OH
|
455
|
+
it 'does not typecast non-time values' do
|
456
|
+
@course.ends_at = 'not-time'
|
457
|
+
@course['ends_at'].should eql('not-time')
|
458
|
+
end
|
459
|
+
end
|
460
|
+
end
|
461
|
+
|
462
|
+
describe 'when type primitive is a Class' do
|
463
|
+
it 'returns same value if a class' do
|
464
|
+
value = Course
|
465
|
+
@course.klass = value
|
466
|
+
@course['klass'].should equal(value)
|
467
|
+
end
|
468
|
+
|
469
|
+
it 'returns the class if found' do
|
470
|
+
@course.klass = 'Course'
|
471
|
+
@course['klass'].should eql(Course)
|
472
|
+
end
|
473
|
+
|
474
|
+
it 'does not typecast non-class values' do
|
475
|
+
@course.klass = 'NoClass'
|
476
|
+
@course['klass'].should eql('NoClass')
|
477
|
+
end
|
478
|
+
end
|
479
|
+
|
480
|
+
describe 'when type primitive is a Boolean' do
|
481
|
+
|
482
|
+
[ true, 'true', 'TRUE', '1', 1, 't', 'T' ].each do |value|
|
483
|
+
it "returns true when value is #{value.inspect}" do
|
484
|
+
@course.active = value
|
485
|
+
@course['active'].should be_true
|
486
|
+
end
|
487
|
+
end
|
488
|
+
|
489
|
+
[ false, 'false', 'FALSE', '0', 0, 'f', 'F' ].each do |value|
|
490
|
+
it "returns false when value is #{value.inspect}" do
|
491
|
+
@course.active = value
|
492
|
+
@course['active'].should be_false
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
[ 'string', 2, 1.0, BigDecimal('1.0'), DateTime.now, Time.now, Date.today, Class, Object.new, ].each do |value|
|
497
|
+
it "does not typecast value #{value.inspect}" do
|
498
|
+
@course.active = value
|
499
|
+
@course['active'].should equal(value)
|
500
|
+
end
|
501
|
+
end
|
502
|
+
|
503
|
+
it "should respond to requests with ? modifier" do
|
504
|
+
@course.active = nil
|
505
|
+
@course.active?.should be_false
|
506
|
+
@course.active = false
|
507
|
+
@course.active?.should be_false
|
508
|
+
@course.active = true
|
509
|
+
@course.active?.should be_true
|
510
|
+
end
|
511
|
+
|
512
|
+
it "should respond to requests with ? modifier on TrueClass" do
|
513
|
+
@course.very_active = nil
|
514
|
+
@course.very_active?.should be_false
|
515
|
+
@course.very_active = false
|
516
|
+
@course.very_active?.should be_false
|
517
|
+
@course.very_active = true
|
518
|
+
@course.very_active?.should be_true
|
519
|
+
end
|
520
|
+
end
|
521
|
+
|
522
|
+
end
|
523
|
+
|
524
|
+
|
@@ -12,45 +12,45 @@ describe "Validations" do
|
|
12
12
|
|
13
13
|
describe "Uniqueness" do
|
14
14
|
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
15
|
+
context "basic" do
|
16
|
+
before(:all) do
|
17
|
+
@objs = ['title 1', 'title 2', 'title 3'].map{|t| WithUniqueValidation.create(:title => t)}
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should validate a new unique document" do
|
21
|
+
@obj = WithUniqueValidation.create(:title => 'title 4')
|
22
|
+
@obj.new?.should_not be_true
|
23
|
+
@obj.should be_valid
|
24
|
+
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
26
|
+
it "should not validate a non-unique document" do
|
27
|
+
@obj = WithUniqueValidation.create(:title => 'title 1')
|
28
|
+
@obj.should_not be_valid
|
29
|
+
@obj.errors[:title].should == ["has already been taken"]
|
30
|
+
end
|
30
31
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
32
|
+
it "should save already created document" do
|
33
|
+
@obj = @objs.first
|
34
|
+
@obj.save.should_not be_false
|
35
|
+
@obj.should be_valid
|
36
|
+
end
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
38
|
+
it "should allow own view to be specified" do
|
39
|
+
# validates_uniqueness_of :code, :view => 'all'
|
40
|
+
WithUniqueValidationView.create(:title => 'title 1', :code => '1234')
|
41
|
+
@obj = WithUniqueValidationView.new(:title => 'title 5', :code => '1234')
|
42
|
+
@obj.should_not be_valid
|
43
|
+
end
|
43
44
|
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
45
|
+
it "should raise an error if specified view does not exist" do
|
46
|
+
WithUniqueValidationView.validates_uniqueness_of :title, :view => 'fooobar'
|
47
|
+
@obj = WithUniqueValidationView.new(:title => 'title 2', :code => '12345')
|
48
|
+
lambda {
|
49
|
+
@obj.valid?
|
50
|
+
}.should raise_error
|
51
|
+
end
|
51
52
|
|
52
|
-
|
53
|
-
it "should not try to create new view" do
|
53
|
+
it "should not try to create new view when already defined" do
|
54
54
|
@obj = @objs[1]
|
55
55
|
@obj.class.should_not_receive('view_by')
|
56
56
|
@obj.class.should_receive('has_view?').and_return(true)
|
@@ -58,7 +58,7 @@ describe "Validations" do
|
|
58
58
|
@obj.valid?
|
59
59
|
end
|
60
60
|
end
|
61
|
-
|
61
|
+
|
62
62
|
context "with a proxy parameter" do
|
63
63
|
it "should be used" do
|
64
64
|
@obj = WithUniqueValidationProxy.new(:title => 'test 6')
|
@@ -77,7 +77,55 @@ describe "Validations" do
|
|
77
77
|
end
|
78
78
|
end
|
79
79
|
|
80
|
-
|
80
|
+
context "when proxied" do
|
81
|
+
it "should lookup the model_proxy" do
|
82
|
+
mp = mock(:ModelProxy)
|
83
|
+
mp.should_receive(:view).and_return({'rows' => []})
|
84
|
+
@obj = WithUniqueValidation.new(:title => 'test 8')
|
85
|
+
@obj.stub!(:model_proxy).twice.and_return(mp)
|
86
|
+
@obj.valid?
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
context "with a scope" do
|
91
|
+
before(:all) do
|
92
|
+
@objs = [['title 1', 1], ['title 2', 1], ['title 3', 1]].map{|t| WithScopedUniqueValidation.create(:title => t[0], :parent_id => t[1])}
|
93
|
+
@objs_nil = [['title 1', nil], ['title 2', nil], ['title 3', nil]].map{|t| WithScopedUniqueValidation.create(:title => t[0], :parent_id => t[1])}
|
94
|
+
end
|
95
|
+
|
96
|
+
it "should create the view" do
|
97
|
+
@objs.first.class.has_view?('by_parent_id_and_title')
|
98
|
+
end
|
99
|
+
|
100
|
+
it "should validate unique document" do
|
101
|
+
@obj = WithScopedUniqueValidation.create(:title => 'title 4', :parent_id => 1)
|
102
|
+
@obj.should be_valid
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should validate unique document outside of scope" do
|
106
|
+
@obj = WithScopedUniqueValidation.create(:title => 'title 1', :parent_id => 2)
|
107
|
+
@obj.should be_valid
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should validate non-unique document" do
|
111
|
+
@obj = WithScopedUniqueValidation.create(:title => 'title 1', :parent_id => 1)
|
112
|
+
@obj.should_not be_valid
|
113
|
+
@obj.errors[:title].should == ["has already been taken"]
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should validate unique document will nil scope" do
|
117
|
+
@obj = WithScopedUniqueValidation.create(:title => 'title 4', :parent_id => nil)
|
118
|
+
@obj.should be_valid
|
119
|
+
end
|
120
|
+
|
121
|
+
it "should validate non-unique document with nil scope" do
|
122
|
+
@obj = WithScopedUniqueValidation.create(:title => 'title 1', :parent_id => nil)
|
123
|
+
@obj.should_not be_valid
|
124
|
+
@obj.errors[:title].should == ["has already been taken"]
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
81
129
|
end
|
82
130
|
|
83
131
|
end
|
data/spec/fixtures/base.rb
CHANGED
@@ -138,4 +138,13 @@ class WithUniqueValidationView < CouchRest::Model::Base
|
|
138
138
|
validates_uniqueness_of :code, :view => 'all'
|
139
139
|
end
|
140
140
|
|
141
|
+
class WithScopedUniqueValidation < CouchRest::Model::Base
|
142
|
+
use_database DB
|
143
|
+
|
144
|
+
property :parent_id
|
145
|
+
property :title
|
146
|
+
|
147
|
+
validates_uniqueness_of :title, :scope => :parent_id
|
148
|
+
end
|
149
|
+
|
141
150
|
|