pod4 0.10.6 → 1.0.0

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.
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