pod4 0.10.6 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.bugs/bugs +2 -1
- data/.bugs/details/b5368c7ef19065fc597b5692314da71772660963.txt +53 -0
- data/.hgtags +1 -0
- data/Gemfile +5 -5
- data/README.md +157 -46
- data/lib/pod4/basic_model.rb +9 -22
- data/lib/pod4/connection.rb +67 -0
- data/lib/pod4/connection_pool.rb +154 -0
- data/lib/pod4/errors.rb +20 -0
- data/lib/pod4/interface.rb +34 -12
- data/lib/pod4/model.rb +32 -27
- data/lib/pod4/nebulous_interface.rb +25 -30
- data/lib/pod4/null_interface.rb +22 -16
- data/lib/pod4/pg_interface.rb +84 -104
- data/lib/pod4/sequel_interface.rb +138 -82
- data/lib/pod4/tds_interface.rb +83 -70
- data/lib/pod4/tweaking.rb +105 -0
- data/lib/pod4/version.rb +1 -1
- data/md/breaking_changes.md +80 -0
- data/spec/common/basic_model_spec.rb +67 -70
- data/spec/common/connection_pool_parallelism_spec.rb +154 -0
- data/spec/common/connection_pool_spec.rb +246 -0
- data/spec/common/connection_spec.rb +129 -0
- data/spec/common/model_ai_missing_id_spec.rb +256 -0
- data/spec/common/model_plus_encrypting_spec.rb +16 -4
- data/spec/common/model_plus_tweaking_spec.rb +128 -0
- data/spec/common/model_plus_typecasting_spec.rb +10 -4
- data/spec/common/model_spec.rb +283 -363
- data/spec/common/nebulous_interface_spec.rb +159 -108
- data/spec/common/null_interface_spec.rb +88 -65
- data/spec/common/sequel_interface_pg_spec.rb +217 -161
- data/spec/common/shared_examples_for_interface.rb +50 -50
- data/spec/jruby/sequel_encrypting_jdbc_pg_spec.rb +1 -1
- data/spec/jruby/sequel_interface_jdbc_ms_spec.rb +3 -3
- data/spec/jruby/sequel_interface_jdbc_pg_spec.rb +3 -23
- data/spec/mri/pg_encrypting_spec.rb +1 -1
- data/spec/mri/pg_interface_spec.rb +311 -223
- data/spec/mri/sequel_encrypting_spec.rb +1 -1
- data/spec/mri/sequel_interface_spec.rb +177 -180
- data/spec/mri/tds_encrypting_spec.rb +1 -1
- data/spec/mri/tds_interface_spec.rb +296 -212
- data/tags +340 -174
- metadata +19 -11
- data/md/fixme.md +0 -3
- data/md/roadmap.md +0 -125
- data/md/typecasting.md +0 -80
- data/spec/common/model_new_validate_spec.rb +0 -204
data/lib/pod4/model.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
require
|
1
|
+
require "octothorpe"
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
3
|
+
require_relative "basic_model"
|
4
|
+
require_relative "errors"
|
5
|
+
require_relative "alert"
|
6
6
|
|
7
7
|
|
8
8
|
module Pod4
|
@@ -112,7 +112,7 @@ module Pod4
|
|
112
112
|
# :id -- otherwise we raise a Pod4Error.
|
113
113
|
#
|
114
114
|
# Note also that while list returns an array of model objects, `read` has _not_ been run
|
115
|
-
# against each object. The data is there, but @model_status == :
|
115
|
+
# against each object. The data is there, but @model_status == :unknown, and validation has not
|
116
116
|
# been run. This is partly for the sake of efficiency, partly to help avoid recursive loops
|
117
117
|
# in validation.
|
118
118
|
#
|
@@ -136,7 +136,7 @@ module Pod4
|
|
136
136
|
|
137
137
|
def test_for_invalid_status(action, status)
|
138
138
|
raise( Pod4Error, "Invalid model status for an action of #{action}", caller ) \
|
139
|
-
if [:
|
139
|
+
if [:unknown, :deleted].include? status
|
140
140
|
|
141
141
|
end
|
142
142
|
|
@@ -159,13 +159,13 @@ module Pod4
|
|
159
159
|
##
|
160
160
|
# Call this to write a new record to the data source.
|
161
161
|
#
|
162
|
-
# Note: create needs to set @
|
162
|
+
# Note: create needs to set @model_id. But interface.create should return it, so that's okay.
|
163
163
|
#
|
164
164
|
def create
|
165
165
|
run_validation(:create)
|
166
166
|
@model_id = interface.create(map_to_interface) unless @model_status == :error
|
167
167
|
|
168
|
-
@model_status = :okay if @model_status == :
|
168
|
+
@model_status = :okay if @model_status == :unknown
|
169
169
|
self
|
170
170
|
rescue Pod4::WeakError
|
171
171
|
add_alert(:error, $!)
|
@@ -183,7 +183,7 @@ module Pod4
|
|
183
183
|
else
|
184
184
|
map_to_model(r)
|
185
185
|
run_validation(:read)
|
186
|
-
@model_status = :okay if @model_status == :
|
186
|
+
@model_status = :okay if @model_status == :unknown
|
187
187
|
end
|
188
188
|
|
189
189
|
self
|
@@ -201,6 +201,10 @@ module Pod4
|
|
201
201
|
clear_alerts; run_validation(:update)
|
202
202
|
interface.update(@model_id, map_to_interface) unless @model_status == :error
|
203
203
|
|
204
|
+
unless interface.id_ai
|
205
|
+
@model_id = instance_variable_get( "@#{interface.id_fld}".to_sym )
|
206
|
+
end
|
207
|
+
|
204
208
|
self
|
205
209
|
rescue Pod4::WeakError
|
206
210
|
add_alert(:error, $!)
|
@@ -259,11 +263,12 @@ module Pod4
|
|
259
263
|
end
|
260
264
|
|
261
265
|
##
|
262
|
-
# Return an Octothorpe of all the attr_columns attributes.
|
266
|
+
# Return an Octothorpe of all the attr_columns attributes. This includes the ID field, whether
|
267
|
+
# or not it has been named in attr_columns.
|
263
268
|
#
|
264
269
|
# Override if you want to return any extra data. (You will need to create a new Octothorpe.)
|
265
270
|
#
|
266
|
-
# See also: `set
|
271
|
+
# See also: `set`
|
267
272
|
#
|
268
273
|
def to_ot
|
269
274
|
Octothorpe.new(to_h)
|
@@ -272,13 +277,11 @@ module Pod4
|
|
272
277
|
##
|
273
278
|
# Used by the interface to set the column values on the model.
|
274
279
|
#
|
275
|
-
# Don't use this to set model attributes from your code; use `set`, instead.
|
276
|
-
#
|
277
280
|
# By default this does exactly the same as `set`. Override it if you want the model to
|
278
281
|
# represent data differently than the data source does -- but then you will have to override
|
279
282
|
# `map_to_interface`, too, to convert the data back.
|
280
283
|
#
|
281
|
-
# See also: `
|
284
|
+
# See also: `map_to_interface'
|
282
285
|
#
|
283
286
|
def map_to_model(ot)
|
284
287
|
merge(ot)
|
@@ -286,19 +289,18 @@ module Pod4
|
|
286
289
|
end
|
287
290
|
|
288
291
|
##
|
289
|
-
#
|
290
|
-
#
|
291
|
-
# Don't use this to get model values in your code; use `to_ot`, instead.# This is called by
|
292
|
-
# model.create and model.update when it needs to write to the data source.
|
292
|
+
# Used by the model to get an OT to pass to the interface on #create and #update.
|
293
293
|
#
|
294
|
-
#
|
295
|
-
#
|
296
|
-
# `map_to_model`.
|
294
|
+
# Override it if you want the model to represent data differently than the data source -- in
|
295
|
+
# which case you also need to override `map_to_model`.
|
297
296
|
#
|
298
297
|
# Bear in mind that any attribute could be nil, and likely will be when `map_to_interface` is
|
299
298
|
# called from the create method.
|
300
299
|
#
|
301
|
-
#
|
300
|
+
# NB: we always pass the ID field to the Interface, regardless of whether the field
|
301
|
+
# autoincrements or whether it's been named in `attr_columns`.
|
302
|
+
#
|
303
|
+
# See also: `map_to_model'
|
302
304
|
#
|
303
305
|
def map_to_interface
|
304
306
|
Octothorpe.new(to_h)
|
@@ -307,12 +309,15 @@ module Pod4
|
|
307
309
|
private
|
308
310
|
|
309
311
|
##
|
310
|
-
# Output a hash of the columns
|
312
|
+
# Output a hash of the columns.
|
313
|
+
# _Always_ include the ID field, even if it's not an attribute.
|
311
314
|
#
|
312
315
|
def to_h
|
313
|
-
columns.each_with_object({}) do |col, hash|
|
316
|
+
h = columns.each_with_object({}) do |col, hash|
|
314
317
|
hash[col] = instance_variable_get("@#{col}".to_sym)
|
315
318
|
end
|
319
|
+
|
320
|
+
{interface.id_fld => @model_id}.merge h
|
316
321
|
end
|
317
322
|
|
318
323
|
##
|
@@ -327,11 +332,11 @@ module Pod4
|
|
327
332
|
end
|
328
333
|
|
329
334
|
##
|
330
|
-
# Call the validate method on the model.
|
331
|
-
# the vmode paramter, as they choose.
|
335
|
+
# Call the validate method on the model.
|
332
336
|
#
|
333
337
|
def run_validation(vmode)
|
334
|
-
|
338
|
+
validate(vmode)
|
339
|
+
self
|
335
340
|
end
|
336
341
|
|
337
342
|
end # of Model
|
@@ -1,5 +1,5 @@
|
|
1
|
-
require_relative
|
2
|
-
require_relative
|
1
|
+
require_relative "interface"
|
2
|
+
require_relative "errors"
|
3
3
|
|
4
4
|
|
5
5
|
module Pod4
|
@@ -8,7 +8,7 @@ module Pod4
|
|
8
8
|
##
|
9
9
|
# An interface to talk to a Nebulous Target.
|
10
10
|
#
|
11
|
-
# Each interface can only speak with one target, designated with #set_target
|
11
|
+
# Each interface can only speak with one target, designated with #set_target. The developer must
|
12
12
|
# also set a unique ID key using #set_id_fld.
|
13
13
|
#
|
14
14
|
# The primary challenge here is to map the CRUDL methods (which interfaces contract to implement)
|
@@ -42,7 +42,7 @@ module Pod4
|
|
42
42
|
# only keys which are symbols are translated to the corresponding values in the record or
|
43
43
|
# selection hash; anything else is passed literally in the Nebulous parameter string.
|
44
44
|
#
|
45
|
-
# When you subclass
|
45
|
+
# When you subclass NebulousInterface, you may want to override some or all of the CRUDL methods
|
46
46
|
# so that your callers can pass specific parameters rather than a hash; the above example
|
47
47
|
# demonstrates this.
|
48
48
|
#
|
@@ -72,9 +72,13 @@ module Pod4
|
|
72
72
|
# self
|
73
73
|
# end
|
74
74
|
#
|
75
|
+
# NB: Connections: Nebulous does not use the Connection class. The user must configure
|
76
|
+
# NebulousStomp themselves, once, when their application starts; but they don't need to do this
|
77
|
+
# before requiring the models. And there is no need for a connection pool.
|
78
|
+
#
|
75
79
|
class NebulousInterface < Interface
|
76
80
|
|
77
|
-
attr_reader :id_fld
|
81
|
+
attr_reader :id_fld, :id_ai
|
78
82
|
|
79
83
|
# The NebulousStomp Message object holding the response from the last message sent, or, nil.
|
80
84
|
attr_reader :response
|
@@ -91,7 +95,6 @@ module Pod4
|
|
91
95
|
# status for that.
|
92
96
|
attr_reader :response_status
|
93
97
|
|
94
|
-
|
95
98
|
Verb = Struct.new(:name, :params)
|
96
99
|
|
97
100
|
|
@@ -118,7 +121,6 @@ module Pod4
|
|
118
121
|
|
119
122
|
def verbs; {}; end
|
120
123
|
|
121
|
-
|
122
124
|
##
|
123
125
|
# Set the name of the Nebulous target in the interface definition
|
124
126
|
#
|
@@ -132,18 +134,22 @@ module Pod4
|
|
132
134
|
raise Pod4Error, "You need to use set_target on your interface"
|
133
135
|
end
|
134
136
|
|
135
|
-
|
136
137
|
##
|
137
138
|
# Set the name of the ID parameter (needs to be in the CRUD verbs param list)
|
138
|
-
def set_id_fld(idFld)
|
139
|
+
def set_id_fld(idFld, opts={})
|
140
|
+
ai = opts.fetch(:autoincrement) { true }
|
139
141
|
define_class_method(:id_fld) {idFld}
|
142
|
+
define_class_method(:id_ai) {!!ai}
|
140
143
|
end
|
141
144
|
|
142
145
|
def id_fld
|
143
146
|
raise Pod4Error, "You need to use set_id_fld"
|
144
147
|
end
|
145
148
|
|
146
|
-
|
149
|
+
def id_ai
|
150
|
+
raise Pod4Error, "You need to use set_id_fld"
|
151
|
+
end
|
152
|
+
|
147
153
|
##
|
148
154
|
# Make sure all of the above is consistent
|
149
155
|
#
|
@@ -164,29 +170,26 @@ module Pod4
|
|
164
170
|
|
165
171
|
end
|
166
172
|
|
167
|
-
|
168
|
-
end
|
169
|
-
##
|
170
|
-
|
173
|
+
end # of class << self
|
171
174
|
|
172
175
|
##
|
173
176
|
# In normal operation, takes no parameters.
|
174
177
|
#
|
175
178
|
# For testing purposes you may pass something here. Whatever it is you pass, it must respond to
|
176
|
-
# a `send` method, take the same parameters as NebulousStomp::Request.new (that is, a
|
177
|
-
# and a message) and return something that behaves like a NebulousStomp::Request.
|
178
|
-
# will be called instead of creating a NebulousStomp::Request directly.
|
179
|
+
# a `send` method, take the same parameters as NebulousStomp::Request.new (that is, a
|
180
|
+
# target and a message) and return something that behaves like a NebulousStomp::Request.
|
181
|
+
# This method will be called instead of creating a NebulousStomp::Request directly.
|
179
182
|
#
|
180
183
|
def initialize(requestObj=nil)
|
181
184
|
@request_object = requestObj # might as well be a reference
|
182
185
|
@response = nil
|
183
186
|
@response_status = nil
|
184
187
|
@id_fld = self.class.id_fld
|
188
|
+
@id_ai = self.class.id_ai
|
185
189
|
|
186
190
|
self.class.validate_params
|
187
191
|
end
|
188
192
|
|
189
|
-
|
190
193
|
##
|
191
194
|
# Pass a parameter string or array (which will be taken as the literal Nebulous parameter) or a
|
192
195
|
# Hash or Octothorpe (which will be interpreted as per your list of keys set in add_verb
|
@@ -215,7 +218,6 @@ module Pod4
|
|
215
218
|
handle_error(e)
|
216
219
|
end
|
217
220
|
|
218
|
-
|
219
221
|
##
|
220
222
|
# Pass a parameter string or an array as the record. returns the ID. We assume that the
|
221
223
|
# response to the create message returns the ID as the parameter part of the success verb. If
|
@@ -224,6 +226,9 @@ module Pod4
|
|
224
226
|
def create(record)
|
225
227
|
raise ArgumentError, 'create takes a Hash or an Octothorpe' unless hashy?(record)
|
226
228
|
|
229
|
+
raise ArgumentError, "ID field missing from record" \
|
230
|
+
if !@id_ai && record[@id_fld].nil? && record[@id_fld.to_s].nil?
|
231
|
+
|
227
232
|
send_message( verb_for(:create), param_string(:create, record), false )
|
228
233
|
@response.params
|
229
234
|
|
@@ -231,7 +236,6 @@ module Pod4
|
|
231
236
|
handle_error(e)
|
232
237
|
end
|
233
238
|
|
234
|
-
|
235
239
|
##
|
236
240
|
# Given the id, return an Octothorpe of the record.
|
237
241
|
#
|
@@ -253,7 +257,6 @@ module Pod4
|
|
253
257
|
Octothorpe.new( @response.body.is_a?(Hash) ? @response.body : {} )
|
254
258
|
end
|
255
259
|
|
256
|
-
|
257
260
|
##
|
258
261
|
# Given an id an a record (Octothorpe or Hash), update the record. Returns self.
|
259
262
|
#
|
@@ -268,7 +271,6 @@ module Pod4
|
|
268
271
|
self
|
269
272
|
end
|
270
273
|
|
271
|
-
|
272
274
|
##
|
273
275
|
# Given an ID, delete the record. Return self.
|
274
276
|
#
|
@@ -283,7 +285,6 @@ module Pod4
|
|
283
285
|
|
284
286
|
self
|
285
287
|
end
|
286
|
-
|
287
288
|
|
288
289
|
##
|
289
290
|
# Bonus method: chain this method before a CRUDL method to clear the cache for that parameter
|
@@ -337,10 +338,8 @@ module Pod4
|
|
337
338
|
handle_error(err)
|
338
339
|
end
|
339
340
|
|
340
|
-
|
341
341
|
private
|
342
342
|
|
343
|
-
|
344
343
|
##
|
345
344
|
# Given :create, :read, :update, :delete or :list, return the Nebulous verb
|
346
345
|
#
|
@@ -348,7 +347,6 @@ module Pod4
|
|
348
347
|
self.class.verbs[action].name
|
349
348
|
end
|
350
349
|
|
351
|
-
|
352
350
|
##
|
353
351
|
# Work out the parameter string based on the corresponding #set_Verb call. Insert the ID value
|
354
352
|
# if given
|
@@ -365,7 +363,6 @@ module Pod4
|
|
365
363
|
para.join(',')
|
366
364
|
end
|
367
365
|
|
368
|
-
|
369
366
|
##
|
370
367
|
# Deal with any exceptions that are raised.
|
371
368
|
#
|
@@ -393,7 +390,6 @@ module Pod4
|
|
393
390
|
|
394
391
|
end
|
395
392
|
|
396
|
-
|
397
393
|
##
|
398
394
|
# A little helper method to create a response object (unless we were given one for testing
|
399
395
|
# purposes), clear the cache if we are supposed to, and then send the message.
|
@@ -417,12 +413,11 @@ module Pod4
|
|
417
413
|
with_cache ? request.send : request.send_no_cache
|
418
414
|
end
|
419
415
|
|
420
|
-
|
421
416
|
def hashy?(obj)
|
422
417
|
obj.kind_of?(Hash) || obj.kind_of?(Octothorpe)
|
423
418
|
end
|
424
419
|
|
425
|
-
end
|
420
|
+
end # of NebulousInterface
|
426
421
|
|
427
422
|
|
428
423
|
end
|
data/lib/pod4/null_interface.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
require
|
1
|
+
require "octothorpe"
|
2
2
|
|
3
|
-
require_relative
|
4
|
-
require_relative
|
3
|
+
require_relative "interface"
|
4
|
+
require_relative "errors"
|
5
5
|
|
6
6
|
|
7
7
|
module Pod4
|
@@ -13,16 +13,22 @@ module Pod4
|
|
13
13
|
# Example:
|
14
14
|
# class TestModel < Pod4::Model
|
15
15
|
# attr_columns :one, :two
|
16
|
-
# set_interface NullInterface.new( :one, :two [ {one: 1, two: 2}
|
16
|
+
# set_interface NullInterface.new( :one, :two [ {one: 1, two: 2},
|
17
|
+
# {one: 2, two: 4} ] )
|
17
18
|
# ...
|
18
19
|
#
|
19
|
-
# The first column passed is taken to be the ID.
|
20
|
-
#
|
20
|
+
# The first column passed is taken to be the ID. We default to autoincrement = true, as
|
21
|
+
# standard. Note that ID values for the initial data are not auto-assigned; you need to specify them.
|
22
|
+
#
|
23
|
+
# You can switch to autoincrement = false by setting `interface.id_ai = false`.
|
24
|
+
#
|
25
|
+
# Note: this is quite different from the behaviour before v1.0, where NullInterface effectively
|
26
|
+
# only allowed you to create a non-autoincrement interface.
|
21
27
|
#
|
22
28
|
class NullInterface < Interface
|
23
29
|
|
24
|
-
attr_reader
|
25
|
-
|
30
|
+
attr_reader :id_fld
|
31
|
+
attr_accessor :id_ai
|
26
32
|
|
27
33
|
##
|
28
34
|
# Initialise the interface by passing it a list of columns and an array of hashes to fill them.
|
@@ -33,12 +39,12 @@ module Pod4
|
|
33
39
|
@cols = cols.dup.map(&:to_sym)
|
34
40
|
@data = Array.new(data.dup).flatten
|
35
41
|
@id_fld = @cols.first
|
42
|
+
@id_ai = true
|
36
43
|
|
37
44
|
rescue => e
|
38
45
|
handle_error(e)
|
39
46
|
end
|
40
47
|
|
41
|
-
|
42
48
|
##
|
43
49
|
# Selection is a hash, but only the first key/value pair is honoured.
|
44
50
|
#
|
@@ -56,7 +62,6 @@ module Pod4
|
|
56
62
|
handle_error(e)
|
57
63
|
end
|
58
64
|
|
59
|
-
|
60
65
|
##
|
61
66
|
# Record is a hash of field: value
|
62
67
|
#
|
@@ -65,14 +70,19 @@ module Pod4
|
|
65
70
|
def create(record)
|
66
71
|
raise(ArgumentError, "Create requires an ID") if record.nil? || ! record.respond_to?(:to_h)
|
67
72
|
|
73
|
+
raise(ArgumentError, "Record missing ID field") \
|
74
|
+
if !@id_ai && record[@id_fld].nil? && record[@id_fld.to_s].nil?
|
75
|
+
|
76
|
+
datum = record.to_h
|
77
|
+
datum[@id_fld] = (@data.size + 1) if @id_ai
|
68
78
|
@data << record.to_h
|
69
|
-
|
79
|
+
|
80
|
+
datum[@id_fld]
|
70
81
|
|
71
82
|
rescue => e
|
72
83
|
handle_error(e)
|
73
84
|
end
|
74
85
|
|
75
|
-
|
76
86
|
##
|
77
87
|
# ID is the first column you named in new()
|
78
88
|
#
|
@@ -86,7 +96,6 @@ module Pod4
|
|
86
96
|
handle_error(e)
|
87
97
|
end
|
88
98
|
|
89
|
-
|
90
99
|
##
|
91
100
|
# ID is the first column you named in new(). Record should be a Hash or Octothorpe.
|
92
101
|
# Again, note that we don't care what columns you send us.
|
@@ -103,7 +112,6 @@ module Pod4
|
|
103
112
|
handle_error(e)
|
104
113
|
end
|
105
114
|
|
106
|
-
|
107
115
|
##
|
108
116
|
# ID is that first column
|
109
117
|
#
|
@@ -117,10 +125,8 @@ module Pod4
|
|
117
125
|
handle_error(e)
|
118
126
|
end
|
119
127
|
|
120
|
-
|
121
128
|
private
|
122
129
|
|
123
|
-
|
124
130
|
def handle_error(err, kaller=nil)
|
125
131
|
kaller ||= caller[1..-1]
|
126
132
|
|