pod4 0.6.2
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.
- checksums.yaml +7 -0
- data/.hgignore +18 -0
- data/.hgtags +19 -0
- data/.rspec +4 -0
- data/.ruby-version +1 -0
- data/Gemfile +3 -0
- data/LICENSE.md +21 -0
- data/README.md +556 -0
- data/Rakefile +30 -0
- data/lib/pod4/alert.rb +87 -0
- data/lib/pod4/basic_model.rb +137 -0
- data/lib/pod4/errors.rb +80 -0
- data/lib/pod4/interface.rb +110 -0
- data/lib/pod4/metaxing.rb +66 -0
- data/lib/pod4/model.rb +347 -0
- data/lib/pod4/nebulous_interface.rb +408 -0
- data/lib/pod4/null_interface.rb +148 -0
- data/lib/pod4/param.rb +29 -0
- data/lib/pod4/pg_interface.rb +460 -0
- data/lib/pod4/sequel_interface.rb +303 -0
- data/lib/pod4/tds_interface.rb +394 -0
- data/lib/pod4/version.rb +3 -0
- data/lib/pod4.rb +54 -0
- data/md/fixme.md +32 -0
- data/md/roadmap.md +69 -0
- data/pod4.gemspec +49 -0
- data/spec/README.md +19 -0
- data/spec/alert_spec.rb +173 -0
- data/spec/basic_model_spec.rb +220 -0
- data/spec/doc_no_pending.rb +5 -0
- data/spec/fixtures/database.rb +13 -0
- data/spec/model_spec.rb +760 -0
- data/spec/nebulous_interface_spec.rb +286 -0
- data/spec/null_interface_spec.rb +153 -0
- data/spec/param_spec.rb +89 -0
- data/spec/pg_interface_spec.rb +452 -0
- data/spec/pod4_spec.rb +88 -0
- data/spec/sequel_interface_spec.rb +466 -0
- data/spec/shared_examples_for_interface.rb +160 -0
- data/spec/spec_helper.rb +25 -0
- data/spec/tds_interface_spec.rb +494 -0
- data/tags +106 -0
- metadata +316 -0
data/spec/model_spec.rb
ADDED
@@ -0,0 +1,760 @@
|
|
1
|
+
require 'octothorpe'
|
2
|
+
|
3
|
+
require 'pod4/model'
|
4
|
+
require 'pod4/null_interface'
|
5
|
+
|
6
|
+
|
7
|
+
##
|
8
|
+
# We define a model class to test, since in normal operation we would never use
|
9
|
+
# Model directly, and since it needs an inner Interface.
|
10
|
+
#
|
11
|
+
# We can't use a mock for the interface -- class definitions fall outside the
|
12
|
+
# RSpec DSL as far as I can tell, so I can neither create a mock here or inject
|
13
|
+
# it. Which means we can't mock the interface in the rest of the test either;
|
14
|
+
# any mock we created would not get called.
|
15
|
+
#
|
16
|
+
# But: we want to test that Model calls Interface correctly.
|
17
|
+
#
|
18
|
+
# We do have what appears to be a perfectly sane way of testing. We can define
|
19
|
+
# an inner class based on the genuinely existing, non-mock NullInterface class;
|
20
|
+
# and then define expectations on it. When we do this, Rspec fails to pass the
|
21
|
+
# call on to the object, unless we specifically say `.and_call_original`
|
22
|
+
# instead of `.and_return`.
|
23
|
+
#
|
24
|
+
# This is actually quite nice, but more than a little confusing when you see it
|
25
|
+
# for the first time. Its use isn't spelled out in the RSpec docs AFAICS.
|
26
|
+
#
|
27
|
+
class CustomerModel < Pod4::Model
|
28
|
+
attr_columns :id, :name, :groups
|
29
|
+
attr_columns :price # specifically testing multiple calls to attr_columns
|
30
|
+
set_interface NullInterface.new(:id, :name, :price, :groups, [])
|
31
|
+
|
32
|
+
def map_to_model(ot)
|
33
|
+
super
|
34
|
+
@groups = @groups ? @groups.split(',') : []
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def map_to_interface
|
39
|
+
x = super
|
40
|
+
g = (x.>>.groups || []).join(',')
|
41
|
+
x.merge(groups: g)
|
42
|
+
end
|
43
|
+
|
44
|
+
def fake_an_alert(*args)
|
45
|
+
add_alert(*args) #private method
|
46
|
+
end
|
47
|
+
|
48
|
+
def validate
|
49
|
+
add_alert(:error, "falling over now") if name == "fall over"
|
50
|
+
end
|
51
|
+
|
52
|
+
def reset_alerts; @alerts = []; end
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
|
57
|
+
describe 'CustomerModel' do
|
58
|
+
|
59
|
+
let(:records) do
|
60
|
+
[ {id: 10, name: 'Gomez', price: 1.23, groups: 'trains' },
|
61
|
+
{id: 20, name: 'Morticia', price: 2.34, groups: 'spanish' },
|
62
|
+
{id: 30, name: 'Wednesday', price: 3.45, groups: 'school' },
|
63
|
+
{id: 40, name: 'Pugsley', price: 4.56, groups: 'trains,school'} ]
|
64
|
+
end
|
65
|
+
|
66
|
+
let(:recordsx) do
|
67
|
+
records.map {|h| h.reject{|k,_| k == :groups} }.flatten
|
68
|
+
end
|
69
|
+
|
70
|
+
let(:records_as_ot) { records.map{|r| Octothorpe.new(r) } }
|
71
|
+
let(:recordsx_as_ot) { recordsx.map{|r| Octothorpe.new(r) } }
|
72
|
+
|
73
|
+
def without_groups(ot)
|
74
|
+
ot.to_h.reject {|k,_| k == :groups}
|
75
|
+
end
|
76
|
+
|
77
|
+
# model is just a plain newly created object that you can call read on.
|
78
|
+
# model2 and model3 are in an identical state - they have been filled with a
|
79
|
+
# read(). We have two so that we can RSpec 'allow' on one and not the other.
|
80
|
+
|
81
|
+
let(:model) { CustomerModel.new(20) }
|
82
|
+
|
83
|
+
let(:model2) do
|
84
|
+
m = CustomerModel.new(30)
|
85
|
+
|
86
|
+
allow( m.interface ).to receive(:read).
|
87
|
+
and_return( Octothorpe.new(records[2]) )
|
88
|
+
|
89
|
+
m.read.or_die
|
90
|
+
end
|
91
|
+
|
92
|
+
let(:model3) do
|
93
|
+
m = CustomerModel.new(40)
|
94
|
+
|
95
|
+
allow( m.interface ).to receive(:read).
|
96
|
+
and_return( Octothorpe.new(records[3]) )
|
97
|
+
|
98
|
+
m.read.or_die
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
##
|
103
|
+
|
104
|
+
|
105
|
+
describe 'Model.attr_columns' do
|
106
|
+
|
107
|
+
it 'requires a list of columns' do
|
108
|
+
expect( CustomerModel ).to respond_to(:attr_columns).with(1).argument
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'exposes the columns just like attr_accessor' do
|
112
|
+
expect( CustomerModel.new ).to respond_to(:id)
|
113
|
+
expect( CustomerModel.new ).to respond_to(:name)
|
114
|
+
expect( CustomerModel.new ).to respond_to(:price)
|
115
|
+
expect( CustomerModel.new ).to respond_to(:groups)
|
116
|
+
expect( CustomerModel.new ).to respond_to(:id=)
|
117
|
+
expect( CustomerModel.new ).to respond_to(:name=)
|
118
|
+
expect( CustomerModel.new ).to respond_to(:price=)
|
119
|
+
expect( CustomerModel.new ).to respond_to(:groups=)
|
120
|
+
end
|
121
|
+
|
122
|
+
# it adds the columns to Model.columns -- covered by the columns test
|
123
|
+
end
|
124
|
+
##
|
125
|
+
|
126
|
+
|
127
|
+
describe 'Model.columns' do
|
128
|
+
it 'lists the columns' do
|
129
|
+
expect( CustomerModel.columns ).to match_array( [:id,:name,:price,:groups] )
|
130
|
+
end
|
131
|
+
end
|
132
|
+
##
|
133
|
+
|
134
|
+
|
135
|
+
describe 'Model.set_interface' do
|
136
|
+
it 'requires an Interface object' do
|
137
|
+
expect( CustomerModel ).to respond_to(:set_interface).with(1).argument
|
138
|
+
end
|
139
|
+
|
140
|
+
# it 'sets interface' - covered by the interface test
|
141
|
+
end
|
142
|
+
##
|
143
|
+
|
144
|
+
|
145
|
+
describe 'Model.interface' do
|
146
|
+
it 'is the interface object' do
|
147
|
+
expect( CustomerModel.interface ).to be_a_kind_of NullInterface
|
148
|
+
expect( CustomerModel.interface.id_fld ).to eq :id
|
149
|
+
end
|
150
|
+
end
|
151
|
+
##
|
152
|
+
|
153
|
+
|
154
|
+
describe 'Model.list' do
|
155
|
+
|
156
|
+
let(:list1) { CustomerModel.list }
|
157
|
+
|
158
|
+
def arr_without_groups(arr)
|
159
|
+
arr
|
160
|
+
.map {|m| without_groups(m.to_ot) }
|
161
|
+
.flatten
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
it 'allows an optional selection parameter' do
|
166
|
+
expect{ CustomerModel.list }.not_to raise_exception
|
167
|
+
expect{ CustomerModel.list(name: 'Betty') }.not_to raise_exception
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'returns an array of CustomerModel records' do
|
171
|
+
expect( CustomerModel.interface ).
|
172
|
+
to receive(:list).with(nil).
|
173
|
+
and_return( records_as_ot )
|
174
|
+
|
175
|
+
expect( list1 ).to be_a_kind_of Array
|
176
|
+
expect( list1 ).to all(be_a_kind_of CustomerModel)
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'returns the data from the interface' do
|
180
|
+
expect( CustomerModel.interface ).
|
181
|
+
to receive(:list).with(nil).
|
182
|
+
and_return(records_as_ot)
|
183
|
+
|
184
|
+
expect( list1.size ).to eq records.size
|
185
|
+
expect( arr_without_groups(list1) ).to include( *recordsx )
|
186
|
+
end
|
187
|
+
|
188
|
+
it 'honours passed selection criteria' do
|
189
|
+
hash = {price: 2.22}
|
190
|
+
|
191
|
+
expect( CustomerModel.interface ).
|
192
|
+
to receive(:list).with(hash).
|
193
|
+
and_return( [Octothorpe.new(records[1])] )
|
194
|
+
|
195
|
+
list2 = CustomerModel.list(hash)
|
196
|
+
expect( list2.size ).to eq 1
|
197
|
+
expect( arr_without_groups(list2).first ).to eq( recordsx[1] )
|
198
|
+
end
|
199
|
+
|
200
|
+
it 'returns an empty array if nothing matches' do
|
201
|
+
hash = {price: 1.23}
|
202
|
+
|
203
|
+
expect( CustomerModel.interface ).
|
204
|
+
to receive(:list).with(hash).
|
205
|
+
and_return([])
|
206
|
+
|
207
|
+
expect( CustomerModel.list(hash) ).to eq []
|
208
|
+
end
|
209
|
+
|
210
|
+
it 'returns an empty array if there are no records' do
|
211
|
+
expect( CustomerModel.list ).to eq []
|
212
|
+
end
|
213
|
+
|
214
|
+
it 'calls map_to_model to set the record data' do
|
215
|
+
allow( CustomerModel.interface ).
|
216
|
+
to receive(:list).
|
217
|
+
and_return(records_as_ot)
|
218
|
+
|
219
|
+
expect( CustomerModel.list.last.groups ).to eq(['trains', 'school'])
|
220
|
+
end
|
221
|
+
|
222
|
+
end
|
223
|
+
##
|
224
|
+
|
225
|
+
|
226
|
+
describe '#new' do
|
227
|
+
|
228
|
+
it 'takes an optional ID' do
|
229
|
+
expect{ CustomerModel.new }.not_to raise_exception
|
230
|
+
expect{ CustomerModel.new(1) }.not_to raise_exception
|
231
|
+
end
|
232
|
+
|
233
|
+
it 'sets the ID attribute' do
|
234
|
+
expect( CustomerModel.new(23).model_id ).to eq 23
|
235
|
+
end
|
236
|
+
|
237
|
+
it 'sets the status to empty' do
|
238
|
+
expect( CustomerModel.new.model_status ).to eq :empty
|
239
|
+
end
|
240
|
+
|
241
|
+
it 'initializes the alerts attribute' do
|
242
|
+
expect( CustomerModel.new.alerts ).to eq([])
|
243
|
+
end
|
244
|
+
|
245
|
+
end
|
246
|
+
##
|
247
|
+
|
248
|
+
|
249
|
+
describe '#interface' do
|
250
|
+
it 'returns the interface set in the class definition, again' do
|
251
|
+
expect( CustomerModel.new.interface ).to be_a_kind_of NullInterface
|
252
|
+
expect( CustomerModel.new.interface.id_fld ).to eq :id
|
253
|
+
end
|
254
|
+
end
|
255
|
+
##
|
256
|
+
|
257
|
+
|
258
|
+
describe '#columns' do
|
259
|
+
it 'returns the attr_columns list from the class definition' do
|
260
|
+
|
261
|
+
expect( CustomerModel.new.columns ).
|
262
|
+
to match_array( [:id,:name,:price,:groups] )
|
263
|
+
|
264
|
+
end
|
265
|
+
end
|
266
|
+
##
|
267
|
+
|
268
|
+
|
269
|
+
describe '#alerts' do
|
270
|
+
it 'returns the list of alerts against the model' do
|
271
|
+
cm = CustomerModel.new
|
272
|
+
cm.fake_an_alert(:warning, :foo, 'one')
|
273
|
+
cm.fake_an_alert(:error, :bar, 'two')
|
274
|
+
|
275
|
+
expect( cm.alerts.size ).to eq 2
|
276
|
+
expect( cm.alerts.map{|a| a.message} ).to match_array(%w|one two|)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
##
|
280
|
+
|
281
|
+
|
282
|
+
describe '#add_alert' do
|
283
|
+
# add_alert is a protected method, which is only supposed to be called
|
284
|
+
# within the validate method of a subclass of Method. So we test it by
|
285
|
+
# calling our alert faking method
|
286
|
+
|
287
|
+
it 'requires type, message or type, field, message' do
|
288
|
+
expect{ model.fake_an_alert }.to raise_exception ArgumentError
|
289
|
+
expect{ model.fake_an_alert(nil) }.to raise_exception ArgumentError
|
290
|
+
expect{ model.fake_an_alert('foo') }.to raise_exception ArgumentError
|
291
|
+
|
292
|
+
expect{ model.fake_an_alert(:error, 'foo') }.not_to raise_exception
|
293
|
+
expect{ model.fake_an_alert(:warning, :name, 'bar') }.
|
294
|
+
not_to raise_exception
|
295
|
+
|
296
|
+
end
|
297
|
+
|
298
|
+
it 'only allows valid types' do
|
299
|
+
[:brian, :werning, nil, :alert, :danger].each do |l|
|
300
|
+
expect{ model.fake_an_alert(l, 'foo') }.to raise_exception ArgumentError
|
301
|
+
end
|
302
|
+
|
303
|
+
[:warning, :error, :success, :info].each do |l|
|
304
|
+
expect{ model.fake_an_alert(l, 'foo') }.not_to raise_exception
|
305
|
+
end
|
306
|
+
|
307
|
+
end
|
308
|
+
|
309
|
+
it 'creates an Alert and adds it to @alerts' do
|
310
|
+
lurch = 'Dnhhhhhh'
|
311
|
+
model.fake_an_alert(:error, :price, lurch)
|
312
|
+
|
313
|
+
expect( model.alerts.size ).to eq 1
|
314
|
+
expect( model.alerts.first ).to be_a_kind_of Pod4::Alert
|
315
|
+
expect( model.alerts.first.message ).to eq lurch
|
316
|
+
end
|
317
|
+
|
318
|
+
it 'sets @model_status if the type is worse than @model_status' do
|
319
|
+
model.fake_an_alert(:warning, :price, 'xoo')
|
320
|
+
expect( model.model_status ).to eq :warning
|
321
|
+
|
322
|
+
model.fake_an_alert(:success, :price, 'flom')
|
323
|
+
expect( model.model_status ).to eq :warning
|
324
|
+
|
325
|
+
model.fake_an_alert(:info, :price, 'flom')
|
326
|
+
expect( model.model_status ).to eq :warning
|
327
|
+
|
328
|
+
model.fake_an_alert(:error, :price, 'qar')
|
329
|
+
expect( model.model_status ).to eq :error
|
330
|
+
|
331
|
+
model.fake_an_alert(:warning, :price, 'drazq')
|
332
|
+
expect( model.model_status ).to eq :error
|
333
|
+
end
|
334
|
+
|
335
|
+
it 'ignores a new alert if identical to an existing one' do
|
336
|
+
lurch = 'Dnhhhhhh'
|
337
|
+
2.times { model.fake_an_alert(:error, :price, lurch) }
|
338
|
+
|
339
|
+
expect( model.alerts.size ).to eq 1
|
340
|
+
end
|
341
|
+
|
342
|
+
end
|
343
|
+
##
|
344
|
+
|
345
|
+
|
346
|
+
describe '#validate' do
|
347
|
+
it 'takes no parameters' do
|
348
|
+
expect{ CustomerModel.new.validate(12) }.to raise_exception ArgumentError
|
349
|
+
expect{ CustomerModel.new.validate }.not_to raise_exception
|
350
|
+
end
|
351
|
+
end
|
352
|
+
##
|
353
|
+
|
354
|
+
|
355
|
+
describe '#set' do
|
356
|
+
|
357
|
+
let (:ot) { records_as_ot[3] }
|
358
|
+
|
359
|
+
it 'takes an Octothorpe or a Hash' do
|
360
|
+
expect{ model.set }.to raise_exception ArgumentError
|
361
|
+
expect{ model.set(nil) }.to raise_exception ArgumentError
|
362
|
+
expect{ model.set(:foo) }.to raise_exception ArgumentError
|
363
|
+
|
364
|
+
expect{ model.set(ot) }.not_to raise_exception
|
365
|
+
end
|
366
|
+
|
367
|
+
it 'returns self' do
|
368
|
+
expect( model.set(ot) ).to eq model
|
369
|
+
end
|
370
|
+
|
371
|
+
it 'sets the attribute columns from the hash' do
|
372
|
+
model.set(ot)
|
373
|
+
|
374
|
+
expect( model.id ).to eq ot.>>.id
|
375
|
+
expect( model.name ).to eq ot.>>.name
|
376
|
+
expect( model.price ).to eq ot.>>.price
|
377
|
+
end
|
378
|
+
|
379
|
+
it 'only sets the attributes on the model that it is given' do
|
380
|
+
otx = Octothorpe.new(name: 'Piggy', price: 98.76, weapon: 'rake')
|
381
|
+
|
382
|
+
expect{ model3.set(otx) }.not_to raise_exception
|
383
|
+
expect( model3.id ).to eq 40
|
384
|
+
expect( model3.name ).to eq 'Piggy'
|
385
|
+
expect( model3.price ).to eq 98.76
|
386
|
+
expect( model3.groups ).to eq( ot.>>.groups.split(',') )
|
387
|
+
end
|
388
|
+
|
389
|
+
end
|
390
|
+
##
|
391
|
+
|
392
|
+
|
393
|
+
describe '#to_ot' do
|
394
|
+
it 'returns an Octothorpe made of the attribute columns' do
|
395
|
+
expect( model.to_ot ).to be_a_kind_of Octothorpe
|
396
|
+
|
397
|
+
expect( model.to_ot.to_h ).
|
398
|
+
to eq( {id: nil, name: nil, price:nil, groups:nil} )
|
399
|
+
|
400
|
+
model.map_to_model(records[1])
|
401
|
+
expect( model.to_ot ).to be_a_kind_of Octothorpe
|
402
|
+
expect( without_groups(model.to_ot) ).to eq recordsx[1]
|
403
|
+
|
404
|
+
model.map_to_model(records_as_ot[2])
|
405
|
+
expect( model.to_ot ).to be_a_kind_of Octothorpe
|
406
|
+
expect( without_groups(model.to_ot) ).to eq recordsx[2]
|
407
|
+
end
|
408
|
+
end
|
409
|
+
##
|
410
|
+
|
411
|
+
|
412
|
+
describe '#map_to_model' do
|
413
|
+
|
414
|
+
it 'sets the columns, with groups as an array' do
|
415
|
+
cm = CustomerModel.new
|
416
|
+
cm.map_to_model(records.last)
|
417
|
+
|
418
|
+
expect( cm.groups ).to eq( ['trains','school'] )
|
419
|
+
end
|
420
|
+
|
421
|
+
end
|
422
|
+
##
|
423
|
+
|
424
|
+
|
425
|
+
describe '#map_to_interface' do
|
426
|
+
|
427
|
+
it 'returns the columns, with groups as a list' do
|
428
|
+
cm = CustomerModel.new
|
429
|
+
cm.map_to_model(records.last)
|
430
|
+
|
431
|
+
expect( cm.map_to_interface.>>.groups ).to eq( 'trains,school' )
|
432
|
+
end
|
433
|
+
|
434
|
+
end
|
435
|
+
##
|
436
|
+
|
437
|
+
|
438
|
+
describe '#raise_exceptions' do
|
439
|
+
|
440
|
+
it 'is also known as .or_die' do
|
441
|
+
cm = CustomerModel.new
|
442
|
+
expect( cm.method(:raise_exceptions) ).to eq( cm.method(:or_die) )
|
443
|
+
end
|
444
|
+
|
445
|
+
it 'raises ValidationError if model status is :error' do
|
446
|
+
model.fake_an_alert(:error, :price, 'qar')
|
447
|
+
expect{ model.raise_exceptions }.to raise_exception Pod4::ValidationError
|
448
|
+
end
|
449
|
+
|
450
|
+
it 'does nothing if model status is not :error' do
|
451
|
+
expect{ model.raise_exceptions }.not_to raise_exception
|
452
|
+
|
453
|
+
model.fake_an_alert(:info, :price, 'qar')
|
454
|
+
expect{ model.raise_exceptions }.not_to raise_exception
|
455
|
+
|
456
|
+
model.fake_an_alert(:success, :price, 'qar')
|
457
|
+
expect{ model.raise_exceptions }.not_to raise_exception
|
458
|
+
|
459
|
+
model.fake_an_alert(:warning, :price, 'qar')
|
460
|
+
expect{ model.raise_exceptions }.not_to raise_exception
|
461
|
+
end
|
462
|
+
|
463
|
+
end
|
464
|
+
##
|
465
|
+
|
466
|
+
|
467
|
+
describe '#create' do
|
468
|
+
|
469
|
+
let (:new_model) { CustomerModel.new }
|
470
|
+
|
471
|
+
it 'takes no parameters' do
|
472
|
+
expect{ CustomerModel.new.create(12) }.to raise_exception ArgumentError
|
473
|
+
expect{ CustomerModel.new.create }.not_to raise_exception
|
474
|
+
end
|
475
|
+
|
476
|
+
it 'returns self' do
|
477
|
+
expect( new_model.create ).to eq new_model
|
478
|
+
end
|
479
|
+
|
480
|
+
it 'calls validate' do
|
481
|
+
expect( new_model ).to receive(:validate)
|
482
|
+
new_model.create
|
483
|
+
end
|
484
|
+
|
485
|
+
it 'calls create on the interface if the record is good' do
|
486
|
+
expect( CustomerModel.interface ).to receive(:create)
|
487
|
+
CustomerModel.new.create
|
488
|
+
|
489
|
+
new_model.fake_an_alert(:warning, :name, 'foo')
|
490
|
+
expect( new_model.interface ).to receive(:create)
|
491
|
+
new_model.create
|
492
|
+
end
|
493
|
+
|
494
|
+
|
495
|
+
it 'doesnt call create on the interface if the record is bad' do
|
496
|
+
new_model.fake_an_alert(:error, :name, 'foo')
|
497
|
+
expect( new_model.interface ).not_to receive(:create)
|
498
|
+
new_model.create
|
499
|
+
end
|
500
|
+
|
501
|
+
it 'sets the ID' do
|
502
|
+
new_model.id = 50
|
503
|
+
new_model.name = "Lurch"
|
504
|
+
new_model.create
|
505
|
+
|
506
|
+
expect( new_model.model_id ).to eq 50
|
507
|
+
end
|
508
|
+
|
509
|
+
it 'sets model status to :okay if it was :empty' do
|
510
|
+
new_model.id = 50
|
511
|
+
new_model.name = "Lurch"
|
512
|
+
new_model.create
|
513
|
+
|
514
|
+
expect( new_model.model_status ).to eq :okay
|
515
|
+
end
|
516
|
+
|
517
|
+
it 'leaves the model status alone if it was not :empty' do
|
518
|
+
new_model.id = 50
|
519
|
+
new_model.name = "Lurch"
|
520
|
+
new_model.create
|
521
|
+
|
522
|
+
new_model.fake_an_alert(:warning, :price, 'qar')
|
523
|
+
expect( new_model.model_status ).to eq :warning
|
524
|
+
end
|
525
|
+
|
526
|
+
it 'calls map_to_interface to get record data' do
|
527
|
+
allow( new_model.interface ).to receive(:create)
|
528
|
+
expect( new_model ).to receive(:map_to_interface)
|
529
|
+
|
530
|
+
new_model.id = 50
|
531
|
+
new_model.name = "Lurch"
|
532
|
+
new_model.create
|
533
|
+
end
|
534
|
+
|
535
|
+
end
|
536
|
+
##
|
537
|
+
|
538
|
+
|
539
|
+
describe '#read' do
|
540
|
+
|
541
|
+
it 'takes no parameters' do
|
542
|
+
expect{ CustomerModel.new.create(12) }.to raise_exception ArgumentError
|
543
|
+
expect{ CustomerModel.new.create }.not_to raise_exception
|
544
|
+
end
|
545
|
+
|
546
|
+
it 'returns self ' do
|
547
|
+
allow( model.interface ).
|
548
|
+
to receive(:read).
|
549
|
+
and_return( records_as_ot.first )
|
550
|
+
|
551
|
+
expect( model.read ).to eq model
|
552
|
+
end
|
553
|
+
|
554
|
+
it 'calls read on the interface' do
|
555
|
+
expect( model.interface ).
|
556
|
+
to receive(:read).
|
557
|
+
and_return( records_as_ot.first )
|
558
|
+
|
559
|
+
model.read
|
560
|
+
end
|
561
|
+
|
562
|
+
it 'calls validate' do
|
563
|
+
allow( model.interface ).
|
564
|
+
to receive(:read).
|
565
|
+
and_return( records_as_ot.first )
|
566
|
+
|
567
|
+
expect( model ).to receive(:validate)
|
568
|
+
model.read
|
569
|
+
end
|
570
|
+
|
571
|
+
it 'sets the attribute columns using map_to_model' do
|
572
|
+
ot = records_as_ot.last
|
573
|
+
allow( model.interface ).to receive(:read).and_return( ot )
|
574
|
+
|
575
|
+
cm = CustomerModel.new(10).read
|
576
|
+
expect( cm.id ).to eq ot.>>.id
|
577
|
+
expect( cm.name ).to eq ot.>>.name
|
578
|
+
expect( cm.price ).to eq ot.>>.price
|
579
|
+
expect( cm.groups ).to be_a_kind_of(Array)
|
580
|
+
expect( cm.groups ).to eq( ot.>>.groups.split(',') )
|
581
|
+
end
|
582
|
+
|
583
|
+
it 'sets model status to :okay if it was :empty' do
|
584
|
+
ot = records_as_ot.last
|
585
|
+
allow( model.interface ).to receive(:read).and_return( ot )
|
586
|
+
|
587
|
+
model.read
|
588
|
+
expect( model.model_status ).to eq :okay
|
589
|
+
end
|
590
|
+
|
591
|
+
it 'leaves the model status alone if it was not :empty' do
|
592
|
+
ot = records_as_ot.last
|
593
|
+
allow( model.interface ).to receive(:read).and_return( ot )
|
594
|
+
|
595
|
+
model.fake_an_alert(:warning, :price, 'qar')
|
596
|
+
model.read
|
597
|
+
expect( model.model_status ).to eq :warning
|
598
|
+
end
|
599
|
+
|
600
|
+
context 'if the interface.read returns an empty Octothorpe' do
|
601
|
+
let(:missing) { CustomerModel.new(99) }
|
602
|
+
|
603
|
+
it 'doesn\'t throw an exception' do
|
604
|
+
expect{ missing.read }.not_to raise_exception
|
605
|
+
end
|
606
|
+
|
607
|
+
it 'raises an error alert' do
|
608
|
+
expect( missing.read.model_status ).to eq :error
|
609
|
+
expect( missing.read.alerts.first.type ).to eq :error
|
610
|
+
end
|
611
|
+
|
612
|
+
end
|
613
|
+
|
614
|
+
end
|
615
|
+
##
|
616
|
+
|
617
|
+
|
618
|
+
describe '#update' do
|
619
|
+
|
620
|
+
before do
|
621
|
+
allow( model2.interface ).
|
622
|
+
to receive(:update).
|
623
|
+
and_return( model2.interface )
|
624
|
+
|
625
|
+
end
|
626
|
+
|
627
|
+
it 'takes no parameters' do
|
628
|
+
expect{ model2.update(12) }.to raise_exception ArgumentError
|
629
|
+
expect{ model2.update }.not_to raise_exception
|
630
|
+
end
|
631
|
+
|
632
|
+
it 'returns self' do
|
633
|
+
expect( model2.update ).to eq model2
|
634
|
+
end
|
635
|
+
|
636
|
+
it 'raises a Pod4Error if model status is :empty' do
|
637
|
+
allow( model.interface ).to receive(:update).and_return( model.interface )
|
638
|
+
|
639
|
+
expect( model.model_status ).to eq :empty
|
640
|
+
expect{ model.update }.to raise_exception Pod4::Pod4Error
|
641
|
+
end
|
642
|
+
|
643
|
+
it 'raises a Pod4Error if model status is :deleted' do
|
644
|
+
model2.delete
|
645
|
+
expect{ model2.update }.to raise_exception Pod4::Pod4Error
|
646
|
+
end
|
647
|
+
|
648
|
+
it 'calls validate' do
|
649
|
+
expect( model2 ).to receive(:validate)
|
650
|
+
model2.update
|
651
|
+
end
|
652
|
+
|
653
|
+
it 'calls update on the interface if the validation passes' do
|
654
|
+
expect( model3.interface ).
|
655
|
+
to receive(:update).
|
656
|
+
and_return( model3.interface )
|
657
|
+
|
658
|
+
model3.update
|
659
|
+
end
|
660
|
+
|
661
|
+
it 'doesnt call update on the interface if the validation fails' do
|
662
|
+
expect( model3.interface ).not_to receive(:update)
|
663
|
+
|
664
|
+
model3.name = "fall over" # triggers validation
|
665
|
+
model3.update
|
666
|
+
end
|
667
|
+
|
668
|
+
it 'calls map_to_interface to get record data' do
|
669
|
+
expect( model3 ).to receive(:map_to_interface)
|
670
|
+
model3.update
|
671
|
+
end
|
672
|
+
|
673
|
+
context 'when the record already has error alerts' do
|
674
|
+
|
675
|
+
it 'passes if there is no longer anything wrong' do
|
676
|
+
expect( model3.interface ).
|
677
|
+
to receive(:update).
|
678
|
+
and_return( model3.interface )
|
679
|
+
|
680
|
+
model3.fake_an_alert(:error, "bad things")
|
681
|
+
model3.update
|
682
|
+
end
|
683
|
+
|
684
|
+
end
|
685
|
+
|
686
|
+
|
687
|
+
end
|
688
|
+
##
|
689
|
+
|
690
|
+
|
691
|
+
describe '#delete' do
|
692
|
+
|
693
|
+
before do
|
694
|
+
allow( model2.interface ).
|
695
|
+
to receive(:delete).
|
696
|
+
and_return( model2.interface )
|
697
|
+
|
698
|
+
end
|
699
|
+
|
700
|
+
it 'takes no parameters' do
|
701
|
+
expect{ model2.delete(12) }.to raise_exception ArgumentError
|
702
|
+
expect{ model2.delete }.not_to raise_exception
|
703
|
+
end
|
704
|
+
|
705
|
+
it 'returns self' do
|
706
|
+
expect( model2.delete ).to eq model2
|
707
|
+
end
|
708
|
+
|
709
|
+
it 'raises a Pod4Error if model status is :empty' do
|
710
|
+
allow( model.interface ).to receive(:delete).and_return( model.interface )
|
711
|
+
|
712
|
+
expect( model.model_status ).to eq :empty
|
713
|
+
expect{ model.delete }.to raise_exception Pod4::Pod4Error
|
714
|
+
end
|
715
|
+
|
716
|
+
it 'raises a Pod4Error if model status is :deleted'do
|
717
|
+
model2.delete
|
718
|
+
expect{ model2.delete }.to raise_exception Pod4::Pod4Error
|
719
|
+
end
|
720
|
+
|
721
|
+
it 'calls validate' do
|
722
|
+
expect( model2 ).to receive(:validate)
|
723
|
+
model2.delete
|
724
|
+
end
|
725
|
+
|
726
|
+
it 'calls delete on the interface if the model status is good' do
|
727
|
+
expect( model3.interface ).
|
728
|
+
to receive(:delete).
|
729
|
+
and_return( model3.interface )
|
730
|
+
|
731
|
+
model3.delete
|
732
|
+
end
|
733
|
+
|
734
|
+
it 'calls delete on the interface if the model status is bad' do
|
735
|
+
expect( model3.interface ).
|
736
|
+
to receive(:delete).
|
737
|
+
and_return( model3.interface )
|
738
|
+
|
739
|
+
model3.fake_an_alert(:error, :price, 'qar')
|
740
|
+
model3.delete
|
741
|
+
end
|
742
|
+
|
743
|
+
it 'still gives you full access to the data after a delete' do
|
744
|
+
model2.delete
|
745
|
+
|
746
|
+
expect( model2.id ).to eq records_as_ot[2].>>.id
|
747
|
+
expect( model2.name ).to eq records_as_ot[2].>>.name
|
748
|
+
expect( model2.price ).to eq records_as_ot[2].>>.price
|
749
|
+
end
|
750
|
+
|
751
|
+
it 'sets status to :deleted' do
|
752
|
+
model2.delete
|
753
|
+
expect( model2.model_status ).to eq :deleted
|
754
|
+
end
|
755
|
+
|
756
|
+
end
|
757
|
+
##
|
758
|
+
|
759
|
+
end
|
760
|
+
|