couchrest_model 1.1.0.beta → 1.1.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
- before(:all) do
16
- @objs = ['title 1', 'title 2', 'title 3'].map{|t| WithUniqueValidation.create(:title => t)}
17
- end
18
-
19
- it "should validate a new unique document" do
20
- @obj = WithUniqueValidation.create(:title => 'title 4')
21
- @obj.new?.should_not be_true
22
- @obj.should be_valid
23
- end
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
- it "should not validate a non-unique document" do
26
- @obj = WithUniqueValidation.create(:title => 'title 1')
27
- @obj.should_not be_valid
28
- @obj.errors[:title].should == ["has already been taken"]
29
- end
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
- it "should save already created document" do
32
- @obj = @objs.first
33
- @obj.save.should_not be_false
34
- @obj.should be_valid
35
- end
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
- it "should allow own view to be specified" do
38
- # validates_uniqueness_of :code, :view => 'all'
39
- WithUniqueValidationView.create(:title => 'title 1', :code => '1234')
40
- @obj = WithUniqueValidationView.new(:title => 'title 5', :code => '1234')
41
- @obj.should_not be_valid
42
- end
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
- it "should raise an error if specified view does not exist" do
45
- WithUniqueValidationView.validates_uniqueness_of :title, :view => 'fooobar'
46
- @obj = WithUniqueValidationView.new(:title => 'title 2', :code => '12345')
47
- lambda {
48
- @obj.valid?
49
- }.should raise_error
50
- end
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
- context "with a pre-defined view" do
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
@@ -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