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
@@ -0,0 +1,286 @@
|
|
1
|
+
require 'pod4/nebulous_interface'
|
2
|
+
|
3
|
+
require 'nebulous'
|
4
|
+
require 'nebulous/nebrequest_null'
|
5
|
+
|
6
|
+
require_relative 'shared_examples_for_interface'
|
7
|
+
|
8
|
+
|
9
|
+
##
|
10
|
+
# In order to conform with 'acts_like an interface', our CRUDL routines must
|
11
|
+
# pass a single value as the record, which we are making an array. When you
|
12
|
+
# subclass NebulousInterface for your model, you don't have to follow that; you
|
13
|
+
# can have a parameter per nebulous parameter, if you want, buy overriding the
|
14
|
+
# CRUDL methods.
|
15
|
+
#
|
16
|
+
class TestNebulousInterface < NebulousInterface
|
17
|
+
set_target 'faketarget'
|
18
|
+
set_id_fld :id
|
19
|
+
|
20
|
+
set_verb :create, 'custcreate', :name, :price
|
21
|
+
set_verb :read, 'custread', :id
|
22
|
+
set_verb :update, 'custupdate', :id, :name, :price
|
23
|
+
set_verb :delete, 'custdelete', :id
|
24
|
+
set_verb :list, 'custlist', :name
|
25
|
+
end
|
26
|
+
##
|
27
|
+
|
28
|
+
|
29
|
+
##
|
30
|
+
# This is the class we will pass an instance of to NebulousInterface to use as
|
31
|
+
# a cut-out for creating Nebulous::NebRequest objects.
|
32
|
+
#
|
33
|
+
# If we pass an instance of this class to NebulousInterface it will call our
|
34
|
+
# send method instead of creating a NebRequest instance by itself. (It expects
|
35
|
+
# send to return a Nebrequest instance, or something that behaves like one.)
|
36
|
+
#
|
37
|
+
# This means we can cut Nebulous out of the loop and don't need a real
|
38
|
+
# responder. We can also check the behaviour of NebulousInterface by using
|
39
|
+
# RSpec 'inspect' syntax on our cutout object.
|
40
|
+
#
|
41
|
+
# We're basically emulating both a responder and a data source here (!)
|
42
|
+
#
|
43
|
+
class FakeRequester
|
44
|
+
|
45
|
+
def initialize(data={}); @data = data; end
|
46
|
+
|
47
|
+
|
48
|
+
def send(verb, paramStr, withCache)
|
49
|
+
array = (paramStr || '').split(',')
|
50
|
+
|
51
|
+
hash1 = { stompHeaders: nil,
|
52
|
+
stompBody: '',
|
53
|
+
verb: '',
|
54
|
+
params: '',
|
55
|
+
desc: '',
|
56
|
+
replyTo: nil,
|
57
|
+
replyId: nil,
|
58
|
+
inReplyTo: nil,
|
59
|
+
contentType: 'application/json' }
|
60
|
+
|
61
|
+
case verb
|
62
|
+
when 'custcreate'
|
63
|
+
id = create(*array)
|
64
|
+
hash2 = {verb: 'success', params: id.to_s}
|
65
|
+
|
66
|
+
when 'custread'
|
67
|
+
record = @data[paramStr.to_i]
|
68
|
+
hash2 = { stompBody: (record ? record.to_json : ''.to_json) }
|
69
|
+
|
70
|
+
when 'custupdate'
|
71
|
+
hash2 = update(*array) ? {verb: 'success'} : {verb: 'error' }
|
72
|
+
|
73
|
+
when 'custdelete'
|
74
|
+
hash2 =
|
75
|
+
if @data.delete(paramStr.to_i)
|
76
|
+
{verb: 'success'}
|
77
|
+
else
|
78
|
+
{verb: 'error'}
|
79
|
+
end
|
80
|
+
|
81
|
+
when 'custlist'
|
82
|
+
subset = @data.values
|
83
|
+
if paramStr && !array[0].empty?
|
84
|
+
subset.select!{|x| x[:name] == array[0] }
|
85
|
+
end
|
86
|
+
hash2 = { stompBody: subset.to_json }
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
req = Nebulous::NebRequestNull.new('faketarget', verb, paramStr)
|
91
|
+
mess = Nebulous::Message.from_cache( hash1.merge(hash2).to_json )
|
92
|
+
req.insert_fake_stomp(mess)
|
93
|
+
req
|
94
|
+
end
|
95
|
+
|
96
|
+
|
97
|
+
def create(name, price)
|
98
|
+
id = @data.keys.sort.last.to_i + 1
|
99
|
+
@data[id] = {id: id, name: name, price: price.to_f}
|
100
|
+
id
|
101
|
+
end
|
102
|
+
|
103
|
+
|
104
|
+
def update(id, name, price)
|
105
|
+
return nil unless @data[id.to_i]
|
106
|
+
@data[id.to_i] = {id: id.to_i, name: name, price: price.to_f}
|
107
|
+
end
|
108
|
+
|
109
|
+
end
|
110
|
+
##
|
111
|
+
|
112
|
+
|
113
|
+
|
114
|
+
describe TestNebulousInterface do
|
115
|
+
|
116
|
+
def init_nebulous
|
117
|
+
stomp_hash = { hosts: [{ login: 'guest',
|
118
|
+
passcode: 'guest',
|
119
|
+
host: '10.0.0.150',
|
120
|
+
port: 61613,
|
121
|
+
ssl: false }],
|
122
|
+
reliable: false }
|
123
|
+
|
124
|
+
# We turn Redis off for this test; we're not testing Nebulous here.
|
125
|
+
Nebulous.init( :stompConnectHash => stomp_hash,
|
126
|
+
:redisConnectHash => {},
|
127
|
+
:messageTimeout => 5,
|
128
|
+
:cacheTimeout => 20 )
|
129
|
+
|
130
|
+
Nebulous.add_target( :faketarget,
|
131
|
+
:sendQueue => "/queue/fake.in",
|
132
|
+
:receiveQueue => "/queue/fake.out",
|
133
|
+
:messageTimeout => 1 )
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
it_behaves_like 'an interface' do
|
139
|
+
let(:record) { {id: 1, name: 'percy', price: 1.23} }
|
140
|
+
|
141
|
+
let(:interface) do
|
142
|
+
init_nebulous
|
143
|
+
TestNebulousInterface.new( FakeRequester.new )
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
##
|
148
|
+
|
149
|
+
|
150
|
+
let(:data) do
|
151
|
+
{ 1 => {id: 1, name: 'Barney', price: 1.11},
|
152
|
+
2 => {id: 2, name: 'Fred', price: 2.22},
|
153
|
+
3 => {id: 3, name: 'Betty', price: 3.33} }
|
154
|
+
end
|
155
|
+
|
156
|
+
let(:interface) do
|
157
|
+
init_nebulous
|
158
|
+
TestNebulousInterface.new( FakeRequester.new(data) )
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
describe '#new' do
|
163
|
+
|
164
|
+
it 'requires no parameters' do
|
165
|
+
expect{ TestNebulousInterface.new }.not_to raise_exception
|
166
|
+
end
|
167
|
+
|
168
|
+
end
|
169
|
+
##
|
170
|
+
|
171
|
+
|
172
|
+
describe '#create' do
|
173
|
+
|
174
|
+
let(:hash) { {name: 'Bam-Bam', price: 4.44} }
|
175
|
+
let(:ot) { Octothorpe.new(name: 'Wilma', price: 5.55) }
|
176
|
+
|
177
|
+
it 'creates the record when given a hash' do
|
178
|
+
id = interface.create(hash)
|
179
|
+
|
180
|
+
expect{ interface.read(id) }.not_to raise_exception
|
181
|
+
expect( interface.read(id).to_h ).to include hash
|
182
|
+
end
|
183
|
+
|
184
|
+
it 'creates the record when given an Octothorpe' do
|
185
|
+
id = interface.create(ot)
|
186
|
+
|
187
|
+
expect{ interface.read(id) }.not_to raise_exception
|
188
|
+
expect( interface.read(id).to_h ).to include ot.to_h
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
##
|
193
|
+
|
194
|
+
|
195
|
+
describe '#read' do
|
196
|
+
|
197
|
+
it 'returns the record for the id as an Octothorpe' do
|
198
|
+
expect( interface.read(1) ).to be_a_kind_of Octothorpe
|
199
|
+
expect( interface.read(2).to_h ).
|
200
|
+
to include(name: 'Fred', price: 2.22)
|
201
|
+
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'returns an empty Octothorpe if no record matches the ID' do
|
205
|
+
expect{ interface.read(99) }.not_to raise_exception
|
206
|
+
expect( interface.read(99) ).to be_a_kind_of Octothorpe
|
207
|
+
expect( interface.read(99) ).to be_empty
|
208
|
+
end
|
209
|
+
|
210
|
+
end
|
211
|
+
##
|
212
|
+
|
213
|
+
|
214
|
+
|
215
|
+
describe '#list' do
|
216
|
+
|
217
|
+
it 'has an optional selection parameter, a hash' do
|
218
|
+
expect{ interface.list }.not_to raise_exception
|
219
|
+
expect{ interface.list(name: 'Barney') }.not_to raise_exception
|
220
|
+
end
|
221
|
+
|
222
|
+
it 'returns an array of Octothorpes that match the records' do
|
223
|
+
arr = interface.list.map(&:to_h)
|
224
|
+
expect( arr ).to match_array data.values
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'returns a subset of records based on the selection parameter' do
|
228
|
+
expect( interface.list(name: 'Fred').size ).to eq 1
|
229
|
+
|
230
|
+
expect( interface.list(name: 'Betty').first.to_h ).
|
231
|
+
to include(name: 'Betty', price: 3.33)
|
232
|
+
|
233
|
+
end
|
234
|
+
|
235
|
+
it 'returns an empty Array if nothing matches' do
|
236
|
+
expect( interface.list(name: 'Yogi') ).to eq([])
|
237
|
+
end
|
238
|
+
|
239
|
+
it 'returns an empty array if there is no data' do
|
240
|
+
interface.list.each{|x| interface.delete(x[interface.id_fld]) }
|
241
|
+
expect( interface.list ).to eq([])
|
242
|
+
end
|
243
|
+
|
244
|
+
end
|
245
|
+
##
|
246
|
+
|
247
|
+
|
248
|
+
describe '#update' do
|
249
|
+
|
250
|
+
let(:id) { interface.list.first[:id] }
|
251
|
+
|
252
|
+
it 'updates the record at ID with record parameter' do
|
253
|
+
rec = {price: 99.99}
|
254
|
+
interface.update(id, rec)
|
255
|
+
|
256
|
+
expect( interface.read(id).to_h ).to include(rec)
|
257
|
+
end
|
258
|
+
|
259
|
+
end
|
260
|
+
##
|
261
|
+
|
262
|
+
|
263
|
+
describe '#delete' do
|
264
|
+
|
265
|
+
let(:id) { interface.list.first[:id] }
|
266
|
+
|
267
|
+
def list_contains(id)
|
268
|
+
interface.list.find {|x| x[interface.id_fld] == id }
|
269
|
+
end
|
270
|
+
|
271
|
+
it 'raises CantContinue if anything hinky happens with the ID' do
|
272
|
+
expect{ interface.delete(:foo) }.to raise_exception CantContinue
|
273
|
+
expect{ interface.delete(99) }.to raise_exception CantContinue
|
274
|
+
end
|
275
|
+
|
276
|
+
it 'makes the record at ID go away' do
|
277
|
+
expect( list_contains(id) ).to be_truthy
|
278
|
+
interface.delete(id)
|
279
|
+
expect( list_contains(id) ).to be_falsy
|
280
|
+
end
|
281
|
+
|
282
|
+
end
|
283
|
+
##
|
284
|
+
|
285
|
+
|
286
|
+
end
|
@@ -0,0 +1,153 @@
|
|
1
|
+
require 'pod4/null_interface'
|
2
|
+
|
3
|
+
require_relative 'shared_examples_for_interface'
|
4
|
+
|
5
|
+
|
6
|
+
describe NullInterface do
|
7
|
+
|
8
|
+
let(:data) do
|
9
|
+
[ {name: 'Barney', price: 1.11},
|
10
|
+
{name: 'Fred', price: 2.22},
|
11
|
+
{name: 'Betty', price: 3.33} ]
|
12
|
+
end
|
13
|
+
|
14
|
+
let (:interface) { NullInterface.new(:name, :price, data) }
|
15
|
+
|
16
|
+
##
|
17
|
+
|
18
|
+
|
19
|
+
it_behaves_like "an interface" do
|
20
|
+
let(:record) { {name: 'barney', price:1.11} }
|
21
|
+
let(:interface) { NullInterface.new( :name, :price, [record] ) }
|
22
|
+
end
|
23
|
+
|
24
|
+
|
25
|
+
describe '#new' do
|
26
|
+
|
27
|
+
it 'requires a list of columns and an array of hashes' do
|
28
|
+
expect{ NullInterface.new }.to raise_exception ArgumentError
|
29
|
+
expect{ NullInterface.new(nil) }.to raise_exception ArgumentError
|
30
|
+
expect{ NullInterface.new('foo') }.to raise_exception ArgumentError
|
31
|
+
|
32
|
+
expect{ NullInterface.new(:one, [{one:1}]) }.not_to raise_exception
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
##
|
37
|
+
|
38
|
+
|
39
|
+
describe '#create' do
|
40
|
+
|
41
|
+
let(:hash) { {name: 'Bam-Bam', price: 4.44} }
|
42
|
+
let(:ot) { Octothorpe.new(name: 'Wilma', price: 5.55) }
|
43
|
+
|
44
|
+
it 'creates the record when given a hash' do
|
45
|
+
id = interface.create(hash)
|
46
|
+
|
47
|
+
expect{ interface.read(id) }.not_to raise_exception
|
48
|
+
expect( interface.read(id).to_h ).to include hash
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'creates the record when given an Octothorpe' do
|
52
|
+
id = interface.create(ot)
|
53
|
+
|
54
|
+
expect{ interface.read(id) }.not_to raise_exception
|
55
|
+
expect( interface.read(id).to_h ).to include ot.to_h
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
##
|
60
|
+
|
61
|
+
|
62
|
+
describe '#read' do
|
63
|
+
|
64
|
+
it 'returns the record for the id as an Octothorpe' do
|
65
|
+
expect( interface.read('Barney') ).to be_a_kind_of Octothorpe
|
66
|
+
expect( interface.read('Fred').to_h ).
|
67
|
+
to include(name: 'Fred', price: 2.22)
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'returns an empty Octothorpe if no record matches the ID' do
|
72
|
+
expect{ interface.read(:foo) }.not_to raise_exception
|
73
|
+
expect( interface.read(:foo) ).to be_a_kind_of Octothorpe
|
74
|
+
expect( interface.read(:foo) ).to be_empty
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
78
|
+
##
|
79
|
+
|
80
|
+
|
81
|
+
describe '#list' do
|
82
|
+
|
83
|
+
it 'has an optional selection parameter, a hash' do
|
84
|
+
expect{ interface.list }.not_to raise_exception
|
85
|
+
expect{ interface.list(name: 'Barney') }.not_to raise_exception
|
86
|
+
end
|
87
|
+
|
88
|
+
it 'returns an array of Octothorpes that match the records' do
|
89
|
+
arr = interface.list.map(&:to_h)
|
90
|
+
expect( arr ).to match_array data
|
91
|
+
end
|
92
|
+
|
93
|
+
it 'returns a subset of records based on the selection parameter' do
|
94
|
+
expect( interface.list(name: 'Fred').size ).to eq 1
|
95
|
+
|
96
|
+
expect( interface.list(name: 'Betty').first.to_h ).
|
97
|
+
to include(name: 'Betty', price: 3.33)
|
98
|
+
|
99
|
+
end
|
100
|
+
|
101
|
+
it 'returns an empty Array if nothing matches' do
|
102
|
+
expect( interface.list(name: 'Yogi') ).to eq([])
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'returns an empty array if there is no data' do
|
106
|
+
interface.list.each {|x| interface.delete(x[interface.id_fld]) }
|
107
|
+
expect( interface.list ).to eq([])
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
##
|
112
|
+
|
113
|
+
|
114
|
+
describe '#update' do
|
115
|
+
|
116
|
+
let(:id) { interface.list.first[:name] }
|
117
|
+
|
118
|
+
it 'updates the record at ID with record parameter' do
|
119
|
+
record = {price: 99.99}
|
120
|
+
interface.update(id, record)
|
121
|
+
|
122
|
+
expect( interface.read(id).to_h ).to include(record)
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
##
|
127
|
+
|
128
|
+
|
129
|
+
describe '#delete' do
|
130
|
+
|
131
|
+
def list_contains(id)
|
132
|
+
interface.list.find {|x| x[interface.id_fld] == id }
|
133
|
+
end
|
134
|
+
|
135
|
+
let(:id) { interface.list.first[:name] }
|
136
|
+
|
137
|
+
it 'raises CantContinue if anything hinky happens with the ID' do
|
138
|
+
expect{ interface.delete(:foo) }.to raise_exception CantContinue
|
139
|
+
expect{ interface.delete(99) }.to raise_exception CantContinue
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'makes the record at ID go away' do
|
143
|
+
expect( list_contains(id) ).to be_truthy
|
144
|
+
interface.delete(id)
|
145
|
+
expect( list_contains(id) ).to be_falsy
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
##
|
150
|
+
|
151
|
+
|
152
|
+
end
|
153
|
+
|
data/spec/param_spec.rb
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
require 'pod4/param'
|
2
|
+
|
3
|
+
|
4
|
+
describe Param do
|
5
|
+
|
6
|
+
after do
|
7
|
+
Param.reset
|
8
|
+
end
|
9
|
+
|
10
|
+
##
|
11
|
+
|
12
|
+
|
13
|
+
describe 'Param.set' do
|
14
|
+
|
15
|
+
it 'requires a parameter and a value' do
|
16
|
+
# We're not fussed about much validation since this is internal
|
17
|
+
expect{ Param.set }.to raise_exception ArgumentError
|
18
|
+
expect{ Param.set(:foo) }.to raise_exception ArgumentError
|
19
|
+
|
20
|
+
expect{ Param.set(:foo, 'bar') }.not_to raise_exception
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'sets a parameter to a value' do
|
24
|
+
Param.set(:foo, 'bar')
|
25
|
+
expect( Param.params ).to include({foo: 'bar'})
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
##
|
30
|
+
|
31
|
+
|
32
|
+
describe 'Param.get' do
|
33
|
+
|
34
|
+
it 'requires a parameter name' do
|
35
|
+
# We're not fussed about much validation since this is internal
|
36
|
+
expect{ Param.get }.to raise_exception ArgumentError
|
37
|
+
expect{ Param.get(:foo) }.not_to raise_exception
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns a parameter value' do
|
41
|
+
Param.set(:foo, 'bar')
|
42
|
+
expect( Param.get(:foo) ).to eq 'bar'
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'returns nil if the parameter was not set' do
|
46
|
+
expect( Param.get(:baz) ).to eq nil
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
##
|
51
|
+
|
52
|
+
|
53
|
+
describe 'Param.reset' do
|
54
|
+
|
55
|
+
it 'removes all parameters' do
|
56
|
+
# probably only this test program needs this method
|
57
|
+
Param.set(:ermintrude, 'darling')
|
58
|
+
Param.reset
|
59
|
+
expect( Param.params ).to eq({})
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
##
|
64
|
+
|
65
|
+
|
66
|
+
describe 'param.get_all' do
|
67
|
+
|
68
|
+
it 'returns all the parameters as an Octothorpe' do
|
69
|
+
Param.set(:ermintrude, 'darling')
|
70
|
+
Param.set(:dillon, 'zzz')
|
71
|
+
Param.set(:zebedee, 'time for bed')
|
72
|
+
|
73
|
+
expect( Param.get_all ).to be_a_kind_of Octothorpe
|
74
|
+
expect( Param.get_all.to_h ).to include( ermintrude: 'darling',
|
75
|
+
dillon: 'zzz',
|
76
|
+
zebedee: 'time for bed' )
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'still returns an Octothorpe if no parameters were set' do
|
81
|
+
expect( Param.get_all ).to be_a_kind_of Octothorpe
|
82
|
+
expect( Param.get_all.to_h ).to eq({})
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
##
|
87
|
+
|
88
|
+
end
|
89
|
+
|