pod4 0.10.6 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +5 -5
  2. data/.bugs/bugs +2 -1
  3. data/.bugs/details/b5368c7ef19065fc597b5692314da71772660963.txt +53 -0
  4. data/.hgtags +1 -0
  5. data/Gemfile +5 -5
  6. data/README.md +157 -46
  7. data/lib/pod4/basic_model.rb +9 -22
  8. data/lib/pod4/connection.rb +67 -0
  9. data/lib/pod4/connection_pool.rb +154 -0
  10. data/lib/pod4/errors.rb +20 -0
  11. data/lib/pod4/interface.rb +34 -12
  12. data/lib/pod4/model.rb +32 -27
  13. data/lib/pod4/nebulous_interface.rb +25 -30
  14. data/lib/pod4/null_interface.rb +22 -16
  15. data/lib/pod4/pg_interface.rb +84 -104
  16. data/lib/pod4/sequel_interface.rb +138 -82
  17. data/lib/pod4/tds_interface.rb +83 -70
  18. data/lib/pod4/tweaking.rb +105 -0
  19. data/lib/pod4/version.rb +1 -1
  20. data/md/breaking_changes.md +80 -0
  21. data/spec/common/basic_model_spec.rb +67 -70
  22. data/spec/common/connection_pool_parallelism_spec.rb +154 -0
  23. data/spec/common/connection_pool_spec.rb +246 -0
  24. data/spec/common/connection_spec.rb +129 -0
  25. data/spec/common/model_ai_missing_id_spec.rb +256 -0
  26. data/spec/common/model_plus_encrypting_spec.rb +16 -4
  27. data/spec/common/model_plus_tweaking_spec.rb +128 -0
  28. data/spec/common/model_plus_typecasting_spec.rb +10 -4
  29. data/spec/common/model_spec.rb +283 -363
  30. data/spec/common/nebulous_interface_spec.rb +159 -108
  31. data/spec/common/null_interface_spec.rb +88 -65
  32. data/spec/common/sequel_interface_pg_spec.rb +217 -161
  33. data/spec/common/shared_examples_for_interface.rb +50 -50
  34. data/spec/jruby/sequel_encrypting_jdbc_pg_spec.rb +1 -1
  35. data/spec/jruby/sequel_interface_jdbc_ms_spec.rb +3 -3
  36. data/spec/jruby/sequel_interface_jdbc_pg_spec.rb +3 -23
  37. data/spec/mri/pg_encrypting_spec.rb +1 -1
  38. data/spec/mri/pg_interface_spec.rb +311 -223
  39. data/spec/mri/sequel_encrypting_spec.rb +1 -1
  40. data/spec/mri/sequel_interface_spec.rb +177 -180
  41. data/spec/mri/tds_encrypting_spec.rb +1 -1
  42. data/spec/mri/tds_interface_spec.rb +296 -212
  43. data/tags +340 -174
  44. metadata +19 -11
  45. data/md/fixme.md +0 -3
  46. data/md/roadmap.md +0 -125
  47. data/md/typecasting.md +0 -80
  48. data/spec/common/model_new_validate_spec.rb +0 -204
@@ -1,17 +1,16 @@
1
- require 'octothorpe'
1
+ require "octothorpe"
2
2
 
3
3
 
4
4
  ##
5
- # These are the shared tests for all interfaces. To use them you need to
6
- # supply:
5
+ # These are the shared tests for all interfaces. To use them you need to supply:
7
6
  #
8
7
  # * record - a record to insert
9
8
  # * interface - an instance of the interface to call.
10
9
  #
11
10
  # For example (from nebulous_interface_spec):
12
11
  #
13
- # it_behaves_like 'an interface' do
14
- # let(:record) { {id: 1, name: 'percy'} }
12
+ # it_behaves_like "an interface" do
13
+ # let(:record) { {id: 1, name: "percy"} }
15
14
  #
16
15
  # let(:interface) do
17
16
  # init_nebulous
@@ -19,36 +18,45 @@ require 'octothorpe'
19
18
  # end
20
19
  # end
21
20
  #
22
- # 'record' does not have to include every column in your table, and you should
23
- # actively exclude floats (where you cannot guarantee equality) or anything
24
- # that is supposed to render to a BigDecimal. Make sure these fields are NULL
25
- # in the table definition.
21
+ # 'record' does not have to include every column in your table, and you should actively exclude
22
+ # floats (where you cannot guarantee equality) or anything that is supposed to render to a
23
+ # BigDecimal. Make sure these fields are NULL in the table definition.
26
24
  #
27
- # Note that these shared tests only test the common parts of the API that the
28
- # interface exposes to the *model*; they make no assumptions about where your
29
- # test data is coming from, or how you are calling or mocking whatever library
30
- # the interface is an adapter to.
25
+ # Note that these shared tests only test the common parts of the API that the interface exposes to
26
+ # the *model* They don't attempt to test your Interface's communication to it's data access layer,
27
+ # or how it talks to the Connection class. It makes no assumptions about where your test data is
28
+ # coming from, or how you are calling or mocking whatever library the interface is an adapter to.
31
29
  #
32
- # It's up to the individual specs to test that the interface is calling its
33
- # library correctly and deal with all the things specific to that interface -
34
- # which includes how the model calls new() and list(), for example.
30
+ # It's up to the individual specs to test that the interface is calling its library correctly and
31
+ # deal with all the things specific to that interface - which includes how the model calls new()
32
+ # and list(), for example.
35
33
  #
36
- RSpec.shared_examples 'an interface' do
34
+ RSpec.shared_examples "an interface" do
37
35
 
38
36
  let(:record_as_ot) { Octothorpe.new(record) }
39
37
 
40
38
 
41
- describe '#id_fld' do
42
- it 'is an attribute' do
39
+ describe "#id_fld" do
40
+
41
+ it "is an attribute" do
43
42
  expect( interface.id_fld ).not_to be_nil
44
43
  end
45
- end
46
- ##
47
44
 
45
+ end # of #id_fld
46
+
47
+
48
+ describe "#id_ai" do
49
+
50
+ it "is an attribute" do
51
+ expect( interface.id_ai ).not_to be_nil
52
+ end
48
53
 
49
- describe '#create' do
54
+ end # of #id_ai
50
55
 
51
- it 'requires a hash or an Octothorpe' do
56
+
57
+ describe "#create" do
58
+
59
+ it "requires a hash or an Octothorpe" do
52
60
  expect{ interface.create }.to raise_exception ArgumentError
53
61
  expect{ interface.create(nil) }.to raise_exception ArgumentError
54
62
  expect{ interface.create(3) }.to raise_exception ArgumentError
@@ -57,47 +65,43 @@ RSpec.shared_examples 'an interface' do
57
65
  expect{ interface.create(record_as_ot) }.not_to raise_exception
58
66
  end
59
67
 
60
- it 'returns the ID' do
68
+ it "returns the ID" do
61
69
  record_id = interface.create(record)
62
70
  expect{ interface.read(record_id) }.not_to raise_exception
63
71
  expect( interface.read(record_id).to_h ).to include record
64
72
  end
65
73
 
66
- end
67
- ##
74
+ end # of #create
68
75
 
69
76
 
70
- describe '#read' do
77
+ describe "#read" do
71
78
 
72
79
  before do
73
80
  interface.create(record)
74
81
  @id = interface.list.first[interface.id_fld]
75
82
  end
76
83
 
77
- it 'requires an ID' do
84
+ it "requires an ID" do
78
85
  expect{ interface.read }.to raise_exception ArgumentError
79
86
  expect{ interface.read(nil) }.to raise_exception ArgumentError
80
87
 
81
88
  expect{ interface.read(@id) }.not_to raise_exception
82
89
  end
83
90
 
84
- it 'returns an Octothorpe' do
91
+ it "returns an Octothorpe" do
85
92
  expect( interface.read(@id) ).to be_a_kind_of Octothorpe
86
93
  end
87
94
 
88
- end
89
- ##
95
+ end # of #read
90
96
 
91
97
 
92
- describe '#update' do
93
-
98
+ describe "#update" do
94
99
  before do
95
100
  interface.create(record)
96
101
  @id = interface.list.first[interface.id_fld]
97
102
  end
98
103
 
99
-
100
- it 'requires an ID and a record (hash or OT)' do
104
+ it "requires an ID and a record (hash or OT)" do
101
105
  expect{ interface.update }.to raise_exception ArgumentError
102
106
  expect{ interface.update(nil) }.to raise_exception ArgumentError
103
107
  expect{ interface.update(14) }.to raise_exception ArgumentError
@@ -105,55 +109,51 @@ RSpec.shared_examples 'an interface' do
105
109
  expect{ interface.update(@id, record) }.not_to raise_exception
106
110
  end
107
111
 
108
- it 'returns self' do
112
+ it "returns self" do
109
113
  expect( interface.update(@id, record) ).to eq interface
110
114
  end
111
115
 
112
- end
113
- ##
114
-
116
+ end # of #update
115
117
 
116
- describe '#delete' do
117
118
 
119
+ describe "#delete" do
118
120
  before do
119
121
  interface.create(record)
120
122
  @id = interface.list.first[interface.id_fld]
121
123
  end
122
124
 
123
- it 'requires an id' do
125
+ it "requires an id" do
124
126
  expect{ interface.delete }.to raise_exception ArgumentError
125
127
  expect{ interface.delete(nil) }.to raise_exception ArgumentError
126
128
 
127
129
  expect{ interface.delete(@id) }.not_to raise_exception
128
130
  end
129
131
 
130
- it 'returns self' do
132
+ it "returns self" do
131
133
  expect( interface.delete(@id) ).to eq interface
132
134
  end
133
135
 
134
- end
135
- ##
136
+ end # of #delete
136
137
 
137
138
 
138
- describe '#list' do
139
+ describe "#list" do
139
140
 
140
- it 'will allow itself to be called with no parameter' do
141
+ it "will allow itself to be called with no parameter" do
141
142
  expect{ interface.list }.not_to raise_exception
142
143
  end
143
144
 
144
- it 'returns an array of Octothorpes' do
145
+ it "returns an array of Octothorpes" do
145
146
  interface.create(record)
146
147
  expect( interface.list ).to be_a_kind_of Array
147
148
  expect( interface.list.first ).to be_a_kind_of Octothorpe
148
149
  end
149
150
 
150
- it 'has the ID field as one of the Octothorpe keys' do
151
+ it "has the ID field as one of the Octothorpe keys" do
151
152
  interface.create(record)
152
153
  expect( interface.list.first.to_h ).to have_key interface.id_fld
153
154
  end
154
155
 
155
- end
156
- ##
156
+ end # of #list
157
157
 
158
158
 
159
159
  end
@@ -36,7 +36,7 @@ describe "(writing encrypted data via sequel_interface)" do
36
36
  let(:medical_interface_class) do
37
37
  Class.new Pod4::SequelInterface do
38
38
  set_table :medical
39
- set_id_fld :id
39
+ set_id_fld :id, autoincrement: true
40
40
  end
41
41
  end
42
42
 
@@ -19,7 +19,7 @@ describe "SequelInterface (JDBC/MSSQL)" do
19
19
  let(:sequel_interface_class) do
20
20
  Class.new SequelInterface do
21
21
  set_table :customer
22
- set_id_fld :id
22
+ set_id_fld :id, autoincrement: true
23
23
  end
24
24
  end
25
25
 
@@ -27,14 +27,14 @@ describe "SequelInterface (JDBC/MSSQL)" do
27
27
  Class.new SequelInterface do
28
28
  set_schema :public
29
29
  set_table :customer
30
- set_id_fld :id
30
+ set_id_fld :id, autoincrement: true
31
31
  end
32
32
  end
33
33
 
34
34
  let(:prod_interface_class) do
35
35
  Class.new SequelInterface do
36
36
  set_table :product
37
- set_id_fld :code
37
+ set_id_fld :code, autoincrement: false
38
38
  end
39
39
  end
40
40
 
@@ -14,32 +14,12 @@ require 'bigdecimal'
14
14
  require 'jdbc/postgres'
15
15
 
16
16
 
17
- =begin
18
- class TestSequelInterfacePg < SequelInterface
19
- set_table :customer
20
- set_id_fld :id
21
- end
22
-
23
- class SchemaSequelInterfacePg < SequelInterface
24
- set_schema :public
25
- set_table :customer
26
- set_id_fld :id
27
- end
28
-
29
- class ProdSequelInterfacePg < SequelInterface
30
- set_table :product
31
- set_id_fld :code
32
- end
33
- =end
34
-
35
-
36
-
37
17
  describe "SequelInterface (JDBC/Pg)" do
38
18
 
39
19
  let(:sequel_interface_class) do
40
20
  Class.new SequelInterface do
41
21
  set_table :customer
42
- set_id_fld :id
22
+ set_id_fld :id, autoincrement: true
43
23
  end
44
24
  end
45
25
 
@@ -47,14 +27,14 @@ describe "SequelInterface (JDBC/Pg)" do
47
27
  Class.new SequelInterface do
48
28
  set_schema :public
49
29
  set_table :customer
50
- set_id_fld :id
30
+ set_id_fld :id, autoincrement: true
51
31
  end
52
32
  end
53
33
 
54
34
  let(:prod_interface_class) do
55
35
  Class.new SequelInterface do
56
36
  set_table :product
57
- set_id_fld :code
37
+ set_id_fld :code, autoincrement: false
58
38
  end
59
39
  end
60
40
 
@@ -52,7 +52,7 @@ describe "(writing encrypted data via pg_interface)" do
52
52
  include Pod4::Encrypting
53
53
 
54
54
  encrypted_columns :name, :ailment
55
- set_key "dflkasdgklajndgnalkghlgasdgasdghaalsdg"
55
+ set_key "dflkasdgklajnlga"
56
56
  set_iv_column :nonce
57
57
  end
58
58
  end
@@ -1,18 +1,58 @@
1
- require 'pod4/pg_interface'
2
- require 'pg'
1
+ require "pod4/pg_interface"
2
+ require "pg"
3
3
 
4
- require_relative '../common/shared_examples_for_interface'
5
- require_relative '../fixtures/database'
4
+ require_relative "../common/shared_examples_for_interface"
5
+ require_relative "../fixtures/database"
6
6
 
7
7
 
8
8
  describe "PgInterface" do
9
9
 
10
+ def db_setup(connect)
11
+ client = PG.connect(connect)
12
+
13
+ client.exec(%Q|
14
+ drop table if exists customer;
15
+ drop table if exists product;
16
+
17
+ create table customer (
18
+ id serial primary key,
19
+ name text,
20
+ level real null,
21
+ day date null,
22
+ timestamp timestamp null,
23
+ price money null,
24
+ flag boolean null,
25
+ qty numeric null );
26
+
27
+ create table product (
28
+ code text,
29
+ name text );| )
30
+
31
+ ensure
32
+ client.finish if client
33
+ end
34
+
35
+ def fill_data(ifce)
36
+ @data.each{|r| ifce.create(r) }
37
+ end
38
+
39
+ def fill_product_data(ifce)
40
+ ifce.create( {code: "foo", name: "bar"} )
41
+ end
42
+
43
+ def list_contains(ifce, id)
44
+ ifce.list.find {|x| x[ifce.id_fld] == id }
45
+ end
46
+
47
+ def float_price(row)
48
+ row[:price] = row[:price].to_f
49
+ row
50
+ end
51
+
10
52
  let(:pg_interface_class) do
11
53
  Class.new PgInterface do
12
54
  set_table :customer
13
55
  set_id_fld :id
14
-
15
- def stop; close; end # We open a lot of connections, unusually
16
56
  end
17
57
  end
18
58
 
@@ -20,14 +60,14 @@ describe "PgInterface" do
20
60
  Class.new PgInterface do
21
61
  set_schema :public
22
62
  set_table :customer
23
- set_id_fld :id
63
+ set_id_fld :id, autoincrement: true
24
64
  end
25
65
  end
26
66
 
27
67
  let(:prod_interface_class) do
28
68
  Class.new PgInterface do
29
69
  set_table :product
30
- set_id_fld :code
70
+ set_id_fld :code, autoincrement: false
31
71
  end
32
72
  end
33
73
 
@@ -43,76 +83,49 @@ describe "PgInterface" do
43
83
  end
44
84
  end
45
85
 
46
-
47
- def db_setup(connect)
48
- client = PG.connect(connect)
49
-
50
- client.exec(%Q|
51
- drop table if exists customer;
52
- drop table if exists product;
53
-
54
- create table customer (
55
- id serial primary key,
56
- name text,
57
- level real null,
58
- day date null,
59
- timestamp timestamp null,
60
- price money null,
61
- flag boolean null,
62
- qty numeric null );
63
-
64
- create table product (
65
- code text,
66
- name text );| )
67
-
68
- ensure
69
- client.finish if client
70
- end
71
-
72
-
73
- def fill_data(ifce)
74
- @data.each{|r| ifce.create(r) }
86
+ let(:interface) do
87
+ pg_interface_class.new(@pool)
75
88
  end
76
89
 
77
-
78
- def fill_product_data(ifce)
79
- ifce.create( {code: "foo", name: "bar"} )
90
+ let(:prod_interface) do
91
+ prod_interface_class.new(@pool)
80
92
  end
81
93
 
82
-
83
94
  before(:all) do
84
95
  @connect_hash = DB[:pg]
85
96
  db_setup(@connect_hash)
86
97
 
87
98
  @data = []
88
- @data << { name: 'Barney',
99
+ @data << { name: "Barney",
89
100
  level: 1.23,
90
101
  day: Date.parse("2016-01-01"),
91
- timestamp: Time.parse('2015-01-01 12:11'),
102
+ timestamp: Time.parse("2015-01-01 12:11"),
92
103
  price: BigDecimal("1.24"),
93
104
  flag: true,
94
105
  qty: BigDecimal("1.25") }
95
106
 
96
- @data << { name: 'Fred',
107
+ @data << { name: "Fred",
97
108
  level: 2.34,
98
109
  day: Date.parse("2016-02-02"),
99
- timestamp: Time.parse('2015-01-02 12:22'),
110
+ timestamp: Time.parse("2015-01-02 12:22"),
100
111
  price: BigDecimal("2.35"),
101
112
  flag: false,
102
113
  qty: BigDecimal("2.36") }
103
114
 
104
- @data << { name: 'Betty',
115
+ @data << { name: "Betty",
105
116
  level: 3.45,
106
117
  day: Date.parse("2016-03-03"),
107
- timestamp: Time.parse('2015-01-03 12:33'),
118
+ timestamp: Time.parse("2015-01-03 12:33"),
108
119
  price: BigDecimal("3.46"),
109
120
  flag: nil,
110
121
  qty: BigDecimal("3.47") }
111
122
 
123
+ # one connection pool for the whole suite, so it doesn't grab (number of tests) connections.
124
+ @pool = ConnectionPool.new(interface: PgInterface)
125
+ @pool.data_layer_options = @connect_hash
112
126
  end
113
127
 
114
-
115
- before do
128
+ before(:each) do
116
129
  interface.execute(%Q|
117
130
  truncate table customer restart identity;
118
131
  truncate table product;|)
@@ -120,127 +133,147 @@ describe "PgInterface" do
120
133
  end
121
134
 
122
135
 
123
- after do
124
- # We open a lot of connections, unusually
125
- interface.stop if interface
126
- end
127
-
128
-
129
- let(:interface) do
130
- pg_interface_class.new(@connect_hash)
131
- end
132
-
133
- let(:prod_interface) do
134
- prod_interface_class.new(@connect_hash)
135
- end
136
-
137
- #####
138
-
139
-
140
- it_behaves_like 'an interface' do
141
-
136
+ it_behaves_like "an interface" do
142
137
  let(:interface) do
143
138
  pg_interface_class.new(@connect_hash)
144
139
  end
145
140
 
146
- let(:record) { {name: 'Barney'} }
141
+ let(:record) { {name: "Barney"} }
147
142
 
148
- end
149
- ##
143
+ end # of it_behaves_like
150
144
 
151
145
 
152
- describe 'PgInterface.set_schema' do
153
- it 'takes one argument' do
146
+ describe "PgInterface.set_schema" do
147
+
148
+ it "takes one argument" do
154
149
  expect( pg_interface_class ).to respond_to(:set_schema).with(1).argument
155
150
  end
156
- end
157
- ##
151
+
152
+ end # of PgInterface.set_schema
158
153
 
159
154
 
160
- describe 'PgInterface.schema' do
161
- it 'returns the schema' do
155
+ describe "PgInterface.schema" do
156
+
157
+ it "returns the schema" do
162
158
  expect( schema_interface_class.schema ).to eq :public
163
159
  end
164
160
 
165
- it 'is optional' do
161
+ it "is optional" do
166
162
  expect{ pg_interface_class.schema }.not_to raise_exception
167
163
  expect( pg_interface_class.schema ).to eq nil
168
164
  end
169
- end
170
- ##
171
165
 
166
+ end # of PgInterface.schema
172
167
 
173
- describe 'PgInterface.set_table' do
174
- it 'takes one argument' do
168
+
169
+ describe "PgInterface.set_table" do
170
+
171
+ it "takes one argument" do
175
172
  expect( pg_interface_class ).to respond_to(:set_table).with(1).argument
176
173
  end
177
- end
178
- ##
174
+
175
+ end # of PgInterface.set_table
179
176
 
180
177
 
181
- describe 'PgInterface.table' do
182
- it 'returns the table' do
178
+ describe "PgInterface.table" do
179
+
180
+ it "returns the table" do
183
181
  expect( pg_interface_class.table ).to eq :customer
184
182
  end
185
- end
186
- ##
187
183
 
184
+ end # of PgInterface.table
185
+
186
+
187
+ describe "PgInterface.set_id_fld" do
188
188
 
189
- describe 'PgInterface.set_id_fld' do
190
- it 'takes one argument' do
189
+ it "takes one argument" do
191
190
  expect( pg_interface_class ).to respond_to(:set_id_fld).with(1).argument
192
191
  end
193
- end
194
- ##
195
192
 
193
+ it "takes an optional second 'autoincrement' argument" do
194
+ expect{ PgInterface.set_id_fld(:foo, autoincrement: false) }.not_to raise_error
195
+ end
196
196
 
197
- describe 'PgInterface.id_fld' do
198
- it 'returns the ID field name' do
197
+ end # of PgInterface.set_id_fld
198
+
199
+
200
+ describe "PgInterface.id_fld" do
201
+
202
+ it "returns the ID field name" do
199
203
  expect( pg_interface_class.id_fld ).to eq :id
200
204
  end
201
- end
202
- ##
203
205
 
206
+ end # of PgInterface.id_fld
207
+
208
+
209
+ describe "PgInterface.id_ai" do
210
+
211
+ it "returns true if autoincrement is true" do
212
+ expect( schema_interface_class.id_ai ).to eq true
213
+ end
214
+
215
+ it "returns false if autoincrement is false" do
216
+ expect( prod_interface_class.id_ai ).to eq false
217
+ end
218
+
219
+ it "returns true if autoincrement is not specified" do
220
+ expect( pg_interface_class.id_ai ).to eq true
221
+ end
222
+
223
+ end # of PgInterface.id_ai
224
+
225
+
226
+ describe "#new" do
227
+
228
+ it "creates a ConnectionPool when passed a PG connection hash" do
229
+ ifce = pg_interface_class.new(@connect_hash)
230
+ expect( ifce._connection ).to be_a ConnectionPool
231
+ end
232
+
233
+ it "uses the ConnectionPool when given one" do
234
+ pool = ConnectionPool.new(interface: pg_interface_class)
235
+ ifce = pg_interface_class.new(pool)
204
236
 
205
- describe '#new' do
237
+ expect( ifce._connection ).to eq pool
238
+ end
206
239
 
207
- it 'requires a TinyTds connection string' do
240
+ it "raises ArgumentError when passed something else" do
208
241
  expect{ pg_interface_class.new }.to raise_exception ArgumentError
209
242
  expect{ pg_interface_class.new(nil) }.to raise_exception ArgumentError
210
- expect{ pg_interface_class.new('foo') }.to raise_exception ArgumentError
211
-
212
- expect{ pg_interface_class.new(@connect_hash) }.not_to raise_exception
243
+ expect{ pg_interface_class.new("foo") }.to raise_exception ArgumentError
213
244
  end
214
245
 
215
- end
216
- ##
246
+ end # of #new
217
247
 
218
248
 
219
- describe '#quoted_table' do
249
+ describe "#quoted_table" do
220
250
 
221
- it 'returns just the table when the schema is not set' do
251
+ it "returns just the table when the schema is not set" do
222
252
  expect( interface.quoted_table ).to eq( %Q|"customer"| )
223
253
  end
224
254
 
225
- it 'returns the schema plus table when the schema is set' do
255
+ it "returns the schema plus table when the schema is set" do
226
256
  ifce = schema_interface_class.new(@connect_hash)
227
257
  expect( ifce.quoted_table ).to eq( %|"public"."customer"| )
228
258
  end
229
259
 
230
- end
231
- ##
260
+ end # of #quoted_table
232
261
 
233
262
 
234
- describe '#create' do
263
+ describe "#create" do
264
+ let(:hash) { {name: "Bam-Bam", price: 4.44} }
265
+ let(:ot) { Octothorpe.new(name: "Wilma", price: 5.55) }
235
266
 
236
- let(:hash) { {name: 'Bam-Bam', price: 4.44} }
237
- let(:ot) { Octothorpe.new(name: 'Wilma', price: 5.55) }
267
+ it "raises a Pod4::DatabaseError if anything goes wrong" do
268
+ expect{ interface.create(one: "two") }.to raise_exception DatabaseError
269
+ end
238
270
 
239
- it 'raises a Pod4::DatabaseError if anything goes wrong' do
240
- expect{ interface.create(one: 'two') }.to raise_exception DatabaseError
271
+ it "raises an ArgumentError if ID field is missing in hash and not AI" do
272
+ hash = {name: "bar"}
273
+ expect{ prod_interface.create(Octothorpe.new hash) }.to raise_error ArgumentError
241
274
  end
242
275
 
243
- it 'creates the record when given a hash' do
276
+ it "creates the record when given a hash" do
244
277
  # kinda impossible to seperate these two tests
245
278
  id = interface.create(hash)
246
279
 
@@ -248,28 +281,28 @@ describe "PgInterface" do
248
281
  expect( interface.read(id).to_h ).to include hash
249
282
  end
250
283
 
251
- it 'creates the record when given an Octothorpe' do
284
+ it "creates the record when given an Octothorpe" do
252
285
  id = interface.create(ot)
253
286
 
254
287
  expect{ interface.read(id) }.not_to raise_exception
255
288
  expect( interface.read(id).to_h ).to include ot.to_h
256
289
  end
257
290
 
258
- it 'shouldn\'t have a problem with record values of nil' do
259
- record = {name: 'Ranger', price: nil}
291
+ it "has no problem with record values of nil" do
292
+ record = {name: "Ranger", price: nil}
260
293
  expect{ interface.create(record) }.not_to raise_exception
261
294
  id = interface.create(record)
262
295
  expect( interface.read(id).to_h ).to include(record)
263
296
  end
264
297
 
265
- it 'shouldn\'t have a problem with strings containing special characters' do
266
- record = {name: %Q|T'Challa""|, price: nil}
298
+ it "has no problem with strings containing special characters" do
299
+ record = {name: %Q|T"Challa""|, price: nil}
267
300
  expect{ interface.create(record) }.not_to raise_exception
268
301
  id = interface.create(record)
269
302
  expect( interface.read(id).to_h ).to include(record)
270
303
  end
271
304
 
272
- it 'shouldn\'t have a problem with non-integer keys' do
305
+ it "has no problem with non-integer keys" do
273
306
  hash = {code: "foo", name: "bar"}
274
307
  id = prod_interface.create( Octothorpe.new(hash) )
275
308
 
@@ -278,65 +311,79 @@ describe "PgInterface" do
278
311
  expect( prod_interface.read("foo").to_h ).to include hash
279
312
  end
280
313
 
281
- end
282
- ##
314
+ it "calls ConnectionPool#client" do
315
+ expect( interface._connection ).to receive(:client).at_least(:once).and_call_original
316
+ interface.create(ot)
317
+ end
318
+
319
+ it "copes with an OT with an ID field when autoincrement = true" do
320
+ h = {id: nil, name: "Bam-Bam", qty: 4.44}
321
+ expect{ interface.create(h) }.not_to raise_error
322
+
323
+ id = interface.create(h)
324
+ expect( id ).not_to be_nil
325
+ expect{ interface.read(id) }.not_to raise_exception
326
+ expect( interface.read(id).to_h ).to include( {name: "Bam-Bam", qty: 4.44} )
327
+ end
328
+
329
+ end # of #create
283
330
 
284
331
 
285
- describe '#read' do
332
+ describe "#read" do
286
333
  before { fill_data(interface) }
287
334
 
288
- it 'returns the record for the id as an Octothorpe' do
335
+ it "returns the record for the id as an Octothorpe" do
289
336
  rec = interface.read(2)
290
337
  expect( rec ).to be_a_kind_of Octothorpe
291
- expect( rec.>>.name ).to eq 'Fred'
338
+ expect( rec.>>.name ).to eq "Fred"
292
339
  end
293
340
 
294
- it 'raises a Pod4::CantContinue if the ID is bad' do
341
+ it "raises a Pod4::CantContinue if the ID is bad" do
295
342
  expect{ interface.read(:foo) }.to raise_exception CantContinue
296
343
  end
297
344
 
298
- it 'returns an empty Octothorpe if no record matches the ID' do
345
+ it "returns an empty Octothorpe if no record matches the ID" do
299
346
  expect{ interface.read(99) }.not_to raise_exception
300
347
  expect( interface.read(99) ).to be_a_kind_of Octothorpe
301
348
  expect( interface.read(99) ).to be_empty
302
349
  end
303
350
 
304
- it 'returns real fields as Float' do
351
+ it "returns real fields as Float" do
305
352
  level = interface.read(1).>>.level
306
353
 
307
354
  expect( level ).to be_a_kind_of Float
308
355
  expect( level ).to be_within(0.001).of( @data.first[:level] )
309
356
  end
310
357
 
311
- it 'returns date fields as Date' do
358
+ it "returns date fields as Date" do
312
359
  date = interface.read(1).>>.day
313
360
 
314
361
  expect( date ).to be_a_kind_of Date
315
362
  expect( date ).to eq @data.first[:day]
316
363
  end
317
364
 
318
- it 'returns datetime fields as Time' do
365
+ it "returns datetime fields as Time" do
319
366
  timestamp = interface.read(1).>>.timestamp
320
367
 
321
368
  expect( timestamp ).to be_a_kind_of Time
322
369
  expect( timestamp ).to eq @data.first[:timestamp]
323
370
  end
324
371
 
325
- it 'returns numeric fields as BigDecimal' do
372
+ it "returns numeric fields as BigDecimal" do
326
373
  qty = interface.read(1).>>.qty
327
374
 
328
375
  expect( qty ).to be_a_kind_of BigDecimal
329
376
  expect( qty ).to eq @data.first[:qty]
330
377
  end
331
378
 
332
- it 'returns money fields as BigDecimal' do
379
+ it "returns money fields as BigDecimal" do
333
380
  price = interface.read(1).>>.price
334
381
 
335
382
  expect( price ).to be_a_kind_of BigDecimal
336
383
  expect( price ).to eq @data.first[:price]
337
384
  end
338
385
 
339
- it 'returns boolean fields as boolean' do
386
+ it "returns boolean fields as boolean" do
340
387
  [1,2,3].each do |i|
341
388
  flag = interface.read(i).>>.flag
342
389
  expect( [true, false, nil].include? flag ).to be true
@@ -344,7 +391,7 @@ describe "PgInterface" do
344
391
  end
345
392
  end
346
393
 
347
- it 'shouldn\'t have a problem with non-integer keys' do
394
+ it "has no problem with non-integer keys" do
348
395
  # this is a 100% overlap with the create test above...
349
396
  fill_product_data(prod_interface)
350
397
 
@@ -352,260 +399,301 @@ describe "PgInterface" do
352
399
  expect( prod_interface.read("foo").to_h ).to include(code: "foo", name: "bar")
353
400
  end
354
401
 
355
- end
356
- ##
402
+ it "calls ConnectionPool#client" do
403
+ expect( interface._connection ).to receive(:client).at_least(:once).and_call_original
404
+ interface.read(99)
405
+ end
406
+
407
+ end # of #read
357
408
 
358
409
 
359
- describe '#list' do
410
+ describe "#list" do
360
411
  before { fill_data(interface) }
361
412
 
362
- it 'has an optional selection parameter, a hash' do
413
+ it "has an optional selection parameter, a hash" do
363
414
  # Actually it does not have to be a hash, but FTTB we only support that.
364
- expect{ interface.list(name: 'Barney') }.not_to raise_exception
415
+ expect{ interface.list(name: "Barney") }.not_to raise_exception
365
416
  end
366
417
 
367
- it 'returns an array of Octothorpes that match the records' do
418
+ it "returns an array of Octothorpes that match the records" do
368
419
  # convert each OT to a hash and remove the ID key
369
420
  arr = interface.list.map {|ot| x = ot.to_h; x.delete(:id); x }
370
421
 
371
422
  expect( arr ).to match_array @data
372
423
  end
373
424
 
374
- it 'returns a subset of records based on the selection parameter' do
375
- expect( interface.list(name: 'Fred').size ).to eq 1
425
+ it "returns a subset of records based on the selection parameter" do
426
+ expect( interface.list(name: "Fred").size ).to eq 1
376
427
 
377
- expect( interface.list(name: 'Betty').first.to_h ).
378
- to include(name: 'Betty')
428
+ expect( interface.list(name: "Betty").first.to_h ).
429
+ to include(name: "Betty")
379
430
 
380
431
  end
381
432
 
382
- it 'returns an empty Array if nothing matches' do
383
- expect( interface.list(name: 'Yogi') ).to eq([])
433
+ it "returns an empty Array if nothing matches" do
434
+ expect( interface.list(name: "Yogi") ).to eq([])
384
435
  end
385
436
 
386
- it 'raises ArgumentError if the selection criteria is nonsensical' do
387
- expect{ interface.list('foo') }.to raise_exception ArgumentError
437
+ it "raises ArgumentError if the selection criteria is nonsensical" do
438
+ expect{ interface.list("foo") }.to raise_exception ArgumentError
388
439
  end
389
440
 
390
- end
391
- ##
441
+ it "calls ConnectionPool#client" do
442
+ expect( interface._connection ).to receive(:client).at_least(:once).and_call_original
443
+ interface.list
444
+ end
445
+
446
+ end # of #list
392
447
 
393
448
 
394
- describe '#update' do
449
+ describe "#update" do
395
450
  before { fill_data(interface) }
396
451
 
397
452
  let(:id) { interface.list.first[:id] }
398
453
 
399
- def float_price(row)
400
- row[:price] = row[:price].to_f
401
- row
402
- end
403
-
404
- it 'updates the record at ID with record parameter' do
405
- record = {name: 'Booboo', price: 99.99}
454
+ it "updates the record at ID with record parameter" do
455
+ record = {name: "Booboo", price: 99.99}
406
456
  interface.update(id, record)
407
457
 
408
458
  expect( float_price( interface.read(id).to_h ) ).to include(record)
409
459
  end
410
460
 
411
- it 'raises a CantContinue if anything weird happens with the ID' do
412
- expect{ interface.update(99, name: 'Booboo') }.
461
+ it "raises a CantContinue if anything weird happens with the ID" do
462
+ expect{ interface.update(99, name: "Booboo") }.
413
463
  to raise_exception CantContinue
414
464
 
415
465
  end
416
466
 
417
- it 'raises a DatabaseError if anything weird happens with the record' do
418
- expect{ interface.update(id, smarts: 'more') }.
467
+ it "raises a DatabaseError if anything weird happens with the record" do
468
+ expect{ interface.update(id, smarts: "more") }.
419
469
  to raise_exception DatabaseError
420
470
 
421
471
  end
422
472
 
423
- it 'shouldn\'t have a problem with record values of nil' do
424
- record = {name: 'Ranger', price: nil}
473
+ it "has no problem with record values of nil" do
474
+ record = {name: "Ranger", price: nil}
425
475
  expect{ interface.update(id, record) }.not_to raise_exception
426
476
  expect( interface.read(id).to_h ).to include(record)
427
477
  end
428
478
 
429
- it 'shouldn\'t have a problem with strings containing special characters' do
430
- record = {name: %Q|T'Challa""|, price: nil}
479
+ it "has no problem with strings containing special characters" do
480
+ record = {name: %Q|T'Challa"|, price: nil}
431
481
  expect{ interface.update(id, record) }.not_to raise_exception
432
482
  expect( interface.read(id).to_h ).to include(record)
433
483
  end
434
484
 
435
- it 'shouldn\'t have a problem with non-integer keys' do
485
+ it "has no problem with non-integer keys" do
436
486
  fill_product_data(prod_interface)
437
487
  expect{ prod_interface.update("foo", name: "baz") }.not_to raise_error
438
488
  expect( prod_interface.read("foo").to_h[:name] ).to eq "baz"
439
489
  end
440
490
 
441
- end
442
- ##
443
-
491
+ it "calls ConnectionPool#client" do
492
+ expect( interface._connection ).to receive(:client).at_least(:once).and_call_original
493
+ record = {name: "Booboo", price: 99.99}
494
+ interface.update(id, record)
495
+ end
444
496
 
445
- describe '#delete' do
497
+ end # of #update
446
498
 
447
- def list_contains(ifce, id)
448
- ifce.list.find {|x| x[ifce.id_fld] == id }
449
- end
450
499
 
500
+ describe "#delete" do
451
501
  let(:id) { interface.list.first[:id] }
452
502
 
453
503
  before { fill_data(interface) }
454
504
 
455
- it 'raises CantContinue if anything hinky happens with the id' do
505
+ it "raises CantContinue if anything hinky happens with the id" do
456
506
  expect{ interface.delete(:foo) }.to raise_exception CantContinue
457
507
  expect{ interface.delete(99) }.to raise_exception CantContinue
458
508
  end
459
509
 
460
- it 'makes the record at ID go away' do
510
+ it "makes the record at ID go away" do
461
511
  expect( list_contains(interface, id) ).to be_truthy
462
512
  interface.delete(id)
463
513
  expect( list_contains(interface, id) ).to be_falsy
464
514
  end
465
515
 
466
- it 'shouldn\'t have a problem with non-integer keys' do
516
+ it "has no roblem with non-integer keys" do
467
517
  fill_product_data(prod_interface)
468
518
  expect( list_contains(prod_interface, "foo") ).to be_truthy
469
519
  prod_interface.delete("foo")
470
520
  expect( list_contains(prod_interface, "foo") ).to be_falsy
471
521
  end
472
522
 
473
- end
474
- ##
523
+ it "calls ConnectionPool#client" do
524
+ expect( interface._connection ).to receive(:client).at_least(:once).and_call_original
525
+ interface.delete(id)
526
+ end
475
527
 
528
+ end # of #delete
476
529
 
477
- describe '#execute' do
478
530
 
479
- let(:sql) { 'delete from customer where cast(price as numeric) < 2.0;' }
531
+ describe "#execute" do
532
+ let(:sql) { "delete from customer where cast(price as numeric) < 2.0;" }
480
533
 
481
534
  before { fill_data(interface) }
482
535
 
483
- it 'requires an SQL string' do
536
+ it "requires an SQL string" do
484
537
  expect{ interface.execute }.to raise_exception ArgumentError
485
538
  expect{ interface.execute(nil) }.to raise_exception ArgumentError
486
539
  expect{ interface.execute(14) }.to raise_exception ArgumentError
487
540
  end
488
541
 
489
- it 'raises some sort of Pod4 error if it runs into problems' do
490
- expect{ interface.execute('delete from not_a_table') }.
542
+ it "raises some sort of Pod4 error if it runs into problems" do
543
+ expect{ interface.execute("delete from not_a_table") }.
491
544
  to raise_exception Pod4Error
492
545
 
493
546
  end
494
547
 
495
- it 'executes the string' do
548
+ it "executes the string" do
496
549
  expect{ interface.execute(sql) }.not_to raise_exception
497
550
  expect( interface.list.size ).to eq(@data.size - 1)
498
- expect( interface.list.map{|r| r[:name] } ).not_to include 'Barney'
551
+ expect( interface.list.map{|r| r[:name] } ).not_to include "Barney"
499
552
  end
500
553
 
501
- end
502
- ##
554
+ it "calls ConnectionPool#client" do
555
+ expect( interface._connection ).to receive(:client).at_least(:once).and_call_original
556
+ interface.execute(sql)
557
+ end
503
558
 
559
+ end # of #execute
504
560
 
505
- describe '#executep' do
506
561
 
507
- let(:sql) { 'delete from customer where cast(price as numeric) < %s and name = %s;' }
562
+ describe "#executep" do
563
+ let(:sql) { "delete from customer where cast(price as numeric) < %s and name = %s;" }
508
564
 
509
565
  before { fill_data(interface) }
510
566
 
511
- it 'requires an SQL string' do
567
+ it "requires an SQL string" do
512
568
  expect{ interface.executep }.to raise_exception ArgumentError
513
569
  expect{ interface.executep(nil) }.to raise_exception ArgumentError
514
570
  expect{ interface.executep(14) }.to raise_exception ArgumentError
515
571
  end
516
572
 
517
- it 'raises some sort of Pod4 error if it runs into problems' do
518
- expect{ interface.executep('delete from not_a_table where foo = %s', 12) }.
573
+ it "raises some sort of Pod4 error if it runs into problems" do
574
+ expect{ interface.executep("delete from not_a_table where foo = %s", 12) }.
519
575
  to raise_exception Pod4Error
520
576
 
521
577
  end
522
578
 
523
- it 'executes the string with the given parameters' do
524
- expect{ interface.executep(sql, 12.0, 'Barney') }.not_to raise_exception
579
+ it "executes the string with the given parameters" do
580
+ expect{ interface.executep(sql, 12.0, "Barney") }.not_to raise_exception
525
581
  expect( interface.list.size ).to eq(@data.size - 1)
526
- expect( interface.list.map{|r| r[:name] } ).not_to include 'Barney'
582
+ expect( interface.list.map{|r| r[:name] } ).not_to include "Barney"
527
583
  end
528
584
 
529
- end
530
- ##
585
+ it "calls ConnectionPool#client" do
586
+ expect( interface._connection ).to receive(:client).at_least(:once).and_call_original
587
+ interface.executep(sql, 12.0, "Barney")
588
+ end
531
589
 
590
+ end # of #executep
532
591
 
533
- describe '#select' do
534
592
 
593
+ describe "#select" do
535
594
  before { fill_data(interface) }
536
595
 
537
- it 'requires an SQL string' do
596
+ it "requires an SQL string" do
538
597
  expect{ interface.select }.to raise_exception ArgumentError
539
598
  expect{ interface.select(nil) }.to raise_exception ArgumentError
540
599
  expect{ interface.select(14) }.to raise_exception ArgumentError
541
600
  end
542
601
 
543
- it 'raises some sort of Pod4 error if it runs into problems' do
544
- expect{ interface.select('select * from not_a_table') }.
602
+ it "raises some sort of Pod4 error if it runs into problems" do
603
+ expect{ interface.select("select * from not_a_table") }.
545
604
  to raise_exception Pod4Error
546
605
 
547
606
  end
548
607
 
549
- it 'returns the result of the sql' do
550
- sql1 = 'select name from customer where cast(price as numeric) < 2.0;'
551
- sql2 = 'select name from customer where cast(price as numeric) < 0.0;'
608
+ it "returns the result of the sql" do
609
+ sql1 = "select name from customer where cast(price as numeric) < 2.0;"
610
+ sql2 = "select name from customer where cast(price as numeric) < 0.0;"
552
611
 
553
612
  expect{ interface.select(sql1) }.not_to raise_exception
554
- expect( interface.select(sql1) ).to eq( [{name: 'Barney'}] )
613
+ expect( interface.select(sql1) ).to eq( [{name: "Barney"}] )
555
614
  expect( interface.select(sql2) ).to eq( [] )
556
615
  end
557
616
 
558
- it 'works if you pass a non-select' do
617
+ it "works if you pass a non-select" do
559
618
  # By which I mean: still executes the SQL; returns []
560
- sql = 'delete from customer where cast(price as numeric) < 2.0;'
619
+ sql = "delete from customer where cast(price as numeric) < 2.0;"
561
620
  ret = interface.select(sql)
562
621
 
563
622
  expect( interface.list.size ).to eq(@data.size - 1)
564
- expect( interface.list.map{|r| r[:name] } ).not_to include 'Barney'
623
+ expect( interface.list.map{|r| r[:name] } ).not_to include "Barney"
565
624
  expect( ret ).to eq( [] )
566
625
  end
567
626
 
568
- end
569
- ##
627
+ it "calls ConnectionPool#client" do
628
+ expect( interface._connection ).to receive(:client).at_least(:once).and_call_original
629
+ sql = "select name from customer;"
630
+ interface.select(sql)
631
+ end
570
632
 
633
+ end # of #select
571
634
 
572
- describe '#selectp' do
573
635
 
636
+ describe "#selectp" do
574
637
  before { fill_data(interface) }
575
638
 
576
- it 'requires an SQL string' do
639
+ it "requires an SQL string" do
577
640
  expect{ interface.selectp }.to raise_exception ArgumentError
578
641
  expect{ interface.selectp(nil) }.to raise_exception ArgumentError
579
642
  expect{ interface.selectp(14) }.to raise_exception ArgumentError
580
643
  end
581
644
 
582
- it 'raises some sort of Pod4 error if it runs into problems' do
583
- expect{ interface.selectp('select * from not_a_table where thingy = %s', 12) }.
645
+ it "raises some sort of Pod4 error if it runs into problems" do
646
+ expect{ interface.selectp("select * from not_a_table where thingy = %s", 12) }.
584
647
  to raise_exception Pod4Error
585
648
 
586
649
  end
587
650
 
588
- it 'returns the result of the sql' do
589
- sql = 'select name from customer where cast(price as numeric) < %s;'
651
+ it "returns the result of the sql" do
652
+ sql = "select name from customer where cast(price as numeric) < %s;"
590
653
 
591
654
  expect{ interface.selectp(sql, 2.0) }.not_to raise_exception
592
- expect( interface.selectp(sql, 2.0) ).to eq( [{name: 'Barney'}] )
655
+ expect( interface.selectp(sql, 2.0) ).to eq( [{name: "Barney"}] )
593
656
  expect( interface.selectp(sql, 0.0) ).to eq( [] )
594
657
  end
595
658
 
596
- it 'works if you pass a non-select' do
597
- sql = 'delete from customer where cast(price as numeric) < %s;'
659
+ it "works if you pass a non-select" do
660
+ sql = "delete from customer where cast(price as numeric) < %s;"
598
661
  ret = interface.selectp(sql, 2.0)
599
662
 
600
663
  expect( interface.list.size ).to eq(@data.size - 1)
601
- expect( interface.list.map{|r| r[:name] } ).not_to include 'Barney'
664
+ expect( interface.list.map{|r| r[:name] } ).not_to include "Barney"
602
665
  expect( ret ).to eq( [] )
603
666
  end
604
667
 
668
+ it "calls ConnectionPool#client" do
669
+ expect( interface._connection ).to receive(:client).at_least(:once).and_call_original
670
+ sql = "select name from customer where cast(price as numeric) < %s;"
671
+ interface.selectp(sql, 2.0)
672
+ end
673
+
674
+ end # of #selectp
675
+
676
+
677
+ describe "#new_connection" do
678
+
679
+ it "returns a PG Client object" do
680
+ expect( interface.new_connection @connect_hash ).to be_a PG::Connection
681
+ end
682
+
683
+ end # of #new_connection
605
684
 
606
- end
607
- ##
608
685
 
686
+ describe "#close_connection" do
609
687
 
688
+ it "closes the given PG Client object" do
689
+ client = interface.new_connection(@connect_hash)
690
+
691
+ expect( client ).to receive(:finish)
692
+
693
+ interface.close_connection(client)
694
+ end
695
+
696
+ end # of #close_connection
697
+
610
698
  end
611
699