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
@@ -0,0 +1,246 @@
1
+ require "pod4/connection_pool"
2
+
3
+
4
+ describe Pod4::ConnectionPool do
5
+
6
+ let(:ifce_class) do
7
+ Class.new Pod4::Interface do
8
+ def initialize; end
9
+ def close_connection; end
10
+ def new_connection(opts); @conn; end
11
+
12
+ def set_conn(c); @conn = c; end
13
+ end
14
+ end
15
+
16
+
17
+ describe "#new" do
18
+
19
+ it "accepts some options and stores them as attributes" do
20
+ c = ConnectionPool.new(interface: ifce_class, max_clients: 4, max_wait: 4_000)
21
+
22
+ expect( c.max_clients ).to eq 4
23
+ expect( c.max_wait ).to eq 4_000
24
+ end
25
+
26
+ it "falls back to reasonable defaults" do
27
+ c = ConnectionPool.new(interface: ifce_class)
28
+
29
+ expect( c.max_clients ).to eq 10
30
+ expect( c.max_wait ).to eq nil
31
+ end
32
+
33
+ end # of #new
34
+
35
+
36
+ describe "#client" do
37
+
38
+ context "when there is a client in the pool for this thread" do
39
+ before(:each) do
40
+ @connection = ConnectionPool.new(interface: ifce_class)
41
+ @connection.data_layer_options = "meh"
42
+ @interface = ifce_class.new
43
+ @interface.set_conn "bar"
44
+
45
+ # First call to client for a thread should assign a client to it
46
+ @connection.client(@interface)
47
+ expect( @connection._pool.size ).to eq 1
48
+ expect( @connection._pool.first.thread_id ).to eq Thread.current.object_id
49
+ end
50
+
51
+ it "returns that client" do
52
+ expect( @interface ).not_to receive(:new_connection)
53
+ expect( @connection.client(@interface) ).to eq "bar"
54
+ end
55
+ end # of when there is a client in the pool for this thread
56
+
57
+ context "when there is no client for this thread and a free one in the pool" do
58
+ before(:each) do
59
+ @connection = ConnectionPool.new(interface: ifce_class)
60
+ @connection.data_layer_options = "meh"
61
+ @interface = ifce_class.new
62
+ @interface.set_conn "foo"
63
+
64
+ # Call client to assign a client to the thread; then call close to release it. We end up
65
+ # with a client in the pool which is assigned to no thread.
66
+ @connection.client(@interface)
67
+ @connection.close(@interface)
68
+ expect( @connection._pool.size ).to eq 1
69
+ expect( @connection._pool.first.thread_id ).to be_nil
70
+ end
71
+
72
+ it "returns the free one" do
73
+ expect( @interface ).not_to receive(:new_connection).and_call_original
74
+ expect( @connection.client(@interface) ).to eq "foo"
75
+ end
76
+
77
+ it "assigns the client to this thread id" do
78
+ @connection.client(@interface)
79
+ expect( @connection._pool.size ).to eq 1
80
+ expect( @connection._pool.first.thread_id ).to eq Thread.current.object_id
81
+ end
82
+ end # of when there is no client for this thread and a free one
83
+
84
+ context "when max_clients == nil, there is no client for this thread and none free" do
85
+ before(:each) do
86
+ @connection = ConnectionPool.new(interface: ifce_class)
87
+ @connection.data_layer_options = "meh"
88
+ @interface = ifce_class.new
89
+ @interface.set_conn "foo"
90
+
91
+ # The simplest case for this scenario is an empty pool
92
+ end
93
+
94
+ it "asks the interface to give it a new client and returns that" do
95
+ expect( @interface ).to receive(:new_connection).with("meh").and_call_original
96
+ expect( @connection.client(@interface) ).to eq "foo"
97
+ end
98
+
99
+ it "stores the new client from the interface in the pool against the thread" do
100
+ @connection.client(@interface)
101
+ expect( @connection._pool.size ).to eq 1
102
+ expect( @connection._pool.first.thread_id ).to eq Thread.current.object_id
103
+ end
104
+ end # of when max_clients == nil, there is no client for this thread and none free
105
+
106
+ context "when max_clients != nil, there is no client for this thread and none free" do
107
+
108
+ context "when we're not at the maximum" do
109
+ before(:each) do
110
+ @connection = ConnectionPool.new(interface: ifce_class, max_clients: 1)
111
+ @connection.data_layer_options = "meh"
112
+ @interface = ifce_class.new
113
+ @interface.set_conn "baz"
114
+
115
+ #this is an empty pool again
116
+ end
117
+
118
+ it "asks the interface to give it a new client and returns that" do
119
+ expect( @interface ).to receive(:new_connection).with("meh").and_call_original
120
+ expect( @connection.client(@interface) ).to eq "baz"
121
+ end
122
+
123
+ it "stores the new client from the interface in the pool against the thread" do
124
+ @connection.client(@interface)
125
+ expect( @connection._pool.size ).to eq 1
126
+ expect( @connection._pool.first.thread_id ).to eq Thread.current.object_id
127
+ end
128
+ end
129
+
130
+ context "when we reach the maximum" do
131
+ before(:each) do
132
+ @connection = ConnectionPool.new(interface: ifce_class, max_clients: 1)
133
+ @connection.data_layer_options = "meh"
134
+ @interface = ifce_class.new
135
+ @interface.set_conn "boz"
136
+
137
+ # assign our 1 client in the pool to a different thread
138
+ @thread = Thread.new do
139
+ @connection.client(@interface)
140
+ Thread.stop # pause the thread here until something external restarts it
141
+ sleep 1
142
+ @connection.close(@interface)
143
+ end
144
+ sleep 0.1 until @thread.stop?
145
+
146
+ expect( @connection._pool.size ).to eq 1
147
+ expect( @connection._pool.first.thread_id ).not_to be_nil
148
+ expect( @connection._pool.first.thread_id ).not_to eq Thread.current.object_id
149
+ end
150
+
151
+ after(:each) { @thread&.kill }
152
+
153
+ it "blocks until a thread is free" do
154
+ # This is hard to test! We can do it but we have to make the horrible assumption that we
155
+ # are using the Ruby `loop` keyword, then stub a loop method to override it.
156
+ expect( @connection ).to receive(:loop).and_yield.and_yield.and_yield
157
+
158
+ @connection.client(@interface)
159
+ end
160
+
161
+ it "once a client is released it uses that" do
162
+ @thread.run # free pool in 1 sec (we want to be already running #client when it frees)
163
+ expect( @connection.client(@interface) ).to eq "boz" # ...eventually
164
+ expect( @connection._pool.size ).to eq 1
165
+ expect( @connection._pool.first.thread_id ).to eq Thread.current.object_id
166
+ end
167
+ end # of when we reach the maximum
168
+
169
+ context "when we reach the maximum, max_wait is set and the time is up" do
170
+ before(:each) do
171
+ @connection = ConnectionPool.new(interface: ifce_class, max_clients: 2, max_wait: 1)
172
+ @connection.data_layer_options = "meh"
173
+ @interface = ifce_class.new
174
+ @interface.set_conn "foo"
175
+
176
+ # assign our 2 clients in the pool to a different thread
177
+ @threads = []
178
+ 2.times do
179
+ @threads << Thread.new { @connection.client(@interface); Thread.stop }
180
+ end
181
+ sleep 0.1 until @threads.all?{|t| t.stop? }
182
+
183
+ expect( @connection._pool.size ).to eq 2
184
+ @threads.each do |t|
185
+ expect( t ).not_to be_nil
186
+ expect( t ).not_to eq Thread.current.object_id
187
+ end
188
+ end
189
+
190
+ after(:each) { @threads.each{|t| t.kill} }
191
+
192
+ it "raises a PoolTimeout" do
193
+ expect{ @connection.client(@interface) }.to raise_error Pod4::PoolTimeout
194
+ end
195
+ end # of when we reach the maximum, max_wait is set and the time is up
196
+
197
+ end # of when max_clients != nil, there is no client for this thread and none free
198
+
199
+ end # of #client
200
+
201
+
202
+ describe "#close" do
203
+ before(:each) do
204
+ @connection = ConnectionPool.new(interface: ifce_class)
205
+ @connection.data_layer_options = "meh"
206
+ @interface = ifce_class.new
207
+ @interface.set_conn "brep"
208
+
209
+ @connection.client(@interface)
210
+ expect( @connection._pool.size ).to eq 1
211
+ expect( @connection._pool.first.thread_id ).to eq Thread.current.object_id
212
+ end
213
+
214
+ it "de-assigns the client for this thread from the thread" do
215
+ @connection.close(@interface)
216
+ expect( @connection._pool.size ).to eq 1
217
+ expect( @connection._pool.first.thread_id ).to be_nil
218
+ end
219
+
220
+ end # of #close
221
+
222
+
223
+ describe "#drop" do
224
+ before(:each) do
225
+ @connection = ConnectionPool.new(interface: ifce_class)
226
+ @connection.data_layer_options = "meh"
227
+ @interface = ifce_class.new
228
+ @interface.set_conn "flong"
229
+
230
+ @connection.client(@interface)
231
+ expect( @connection._pool.size ).to eq 1
232
+ expect( @connection._pool.first.thread_id ).to eq Thread.current.object_id
233
+ end
234
+
235
+ it "removes the client object from the pool entirely" do
236
+ @connection.drop(@interface)
237
+ expect( @connection._pool.size ).to eq 0
238
+ end
239
+
240
+ end # of #drop
241
+
242
+
243
+
244
+
245
+ end
246
+
@@ -0,0 +1,129 @@
1
+ require 'pod4/connection'
2
+ require 'pod4/null_interface'
3
+
4
+
5
+ ##
6
+ # I can't make these anonymous classes in an Rspec `let`, because the name of the interface class is
7
+ # passed to the Connection object when it is initialised.
8
+ ##
9
+
10
+ class ConnectionTestingI < Interface
11
+ def initialize; end
12
+ def close_connection; end
13
+ def new_connection(args); {conn: args}; end
14
+ end
15
+
16
+ class ConnectionTestingIBad < Interface
17
+ def initialize; end
18
+ def close_connection; end
19
+ def new_connection(args); end
20
+ end
21
+
22
+
23
+ describe Pod4::Connection do
24
+
25
+ let(:interface) { ConnectionTestingI.new }
26
+ let(:conn) { Pod4::Connection.new(interface: ConnectionTestingI) }
27
+
28
+
29
+ describe "#new" do
30
+
31
+ it "takes a hash" do
32
+ expect{ Pod4::Connection.new }.to raise_error ArgumentError
33
+ expect{ Pod4::Connection.new(:foo) }.to raise_error ArgumentError
34
+ end
35
+
36
+ it "needs :interface, a Pod4::Interface class" do
37
+ expect{ Pod4::Connection.new(interface: "foo") }.to raise_error ArgumentError
38
+ expect{ Pod4::Connection.new(interface: Array) }.to raise_error ArgumentError
39
+ expect{ Pod4::Connection.new(interface: ConnectionTestingI) }.not_to raise_error
40
+
41
+ expect( conn.interface_class ).to eq ConnectionTestingI
42
+ end
43
+
44
+ end # of #new
45
+
46
+
47
+ describe "#data_layer_options" do
48
+
49
+ it "stores an arbitrary object" do
50
+ expect( conn.data_layer_options ).to be_nil
51
+
52
+ conn.data_layer_options = {one: 2, three: 4}
53
+
54
+ expect( conn.data_layer_options ).to eq(one: 2, three: 4)
55
+ end
56
+
57
+ end # of #data_layer_options
58
+
59
+
60
+ describe "#close" do
61
+
62
+ it "raises ArgumentError if given an interface that wasn't the one you passed in #new" do
63
+ i = ConnectionTestingIBad.new
64
+ expect{ conn.close(i) }.to raise_error ArgumentError
65
+ end
66
+
67
+ it "calls close on the interface" do
68
+ expect(interface).to receive(:close_connection)
69
+
70
+ conn.close(interface)
71
+ end
72
+
73
+ it "resets the stored client" do
74
+ conn.close(interface)
75
+
76
+ # Now the stored client should be unset, so a further call to #client should ask the
77
+ # interface for one
78
+ expect(interface).to receive(:new_connection)
79
+
80
+ conn.client(interface)
81
+ end
82
+
83
+ end # of #close
84
+
85
+
86
+ describe "#client" do
87
+
88
+ it "takes an interface object" do
89
+ expect{ conn.client }.to raise_exception ArgumentError
90
+ expect{ conn.client(14) }.to raise_exception ArgumentError
91
+
92
+ expect{ conn.client(interface) }.not_to raise_exception
93
+ end
94
+
95
+ it "raises ArgumentError if given an interface that wasn't the one you passed in #new" do
96
+ i = ConnectionTestingIBad.new
97
+ expect{ conn.client(i) }.to raise_error ArgumentError
98
+ end
99
+
100
+ context "when it has no connection" do
101
+
102
+ it "calls new_connection on the interface" do
103
+ expect(interface).to receive(:new_connection).with("bar").and_call_original
104
+
105
+ conn.data_layer_options = "bar"
106
+
107
+ expect( conn.client(interface) ).to eq(conn: "bar")
108
+ end
109
+
110
+ end
111
+
112
+ context "when it has a connection" do
113
+
114
+ it "returns what it has" do
115
+ # set things up like before so we have an existing connection
116
+ conn.data_layer_options = "foo"
117
+ conn.client(interface)
118
+
119
+ expect(interface).not_to receive(:new_connection)
120
+
121
+ expect( conn.client(interface) ).to eq(conn: "foo")
122
+ end
123
+
124
+ end
125
+
126
+ end # of #client
127
+
128
+
129
+ end
@@ -0,0 +1,256 @@
1
+ require "octothorpe"
2
+
3
+ require "pod4/model"
4
+ require "pod4/null_interface"
5
+
6
+
7
+ ##
8
+ # This test covers a model with an autoincrementing ID but where the ID field is not named as an
9
+ # attribute. Pre-1.0 you _had_ to do it this way. No reason why it would not be an option going
10
+ # forward.
11
+ #
12
+ describe "(Autoincrementing Model with No ID Attribute)" do
13
+
14
+ let(:customer_model_class) do
15
+ Class.new Pod4::Model do
16
+ attr_columns :name, :groups, :price
17
+ set_interface NullInterface.new(:id, :name, :price, :groups,
18
+ [ {id: 1, name: "Gomez", price: 1.23, groups: "trains" },
19
+ {id: 2, name: "Morticia", price: 2.34, groups: "spanish" },
20
+ {id: 3, name: "Wednesday", price: 3.45, groups: "school" },
21
+ {id: 4, name: "Pugsley", price: 4.56, groups: "trains,school"} ] )
22
+
23
+ end
24
+ end
25
+
26
+ let(:records) do
27
+ [ {id: 1, name: "Gomez", price: 1.23, groups: "trains" },
28
+ {id: 2, name: "Morticia", price: 2.34, groups: "spanish" },
29
+ {id: 3, name: "Wednesday", price: 3.45, groups: "school" },
30
+ {id: 4, name: "Pugsley", price: 4.56, groups: "trains,school"} ]
31
+
32
+ end
33
+
34
+ let(:records_as_ot) { records.map{|r| Octothorpe.new(r) } }
35
+
36
+ # model is just a plain newly created object that you can call read on.
37
+ # model2 and model3 are in an identical state - they have been filled with a
38
+ # read(). We have two so that we can RSpec "allow" on one and not the other.
39
+
40
+ let(:model) { customer_model_class.new(2) }
41
+
42
+ let(:model2) do
43
+ m = customer_model_class.new(3)
44
+
45
+ allow( m.interface ).to receive(:read).and_return( Octothorpe.new(records[2]) )
46
+ m.read.or_die
47
+ end
48
+
49
+ let(:model3) do
50
+ m = customer_model_class.new(4)
51
+
52
+ allow( m.interface ).to receive(:read).and_return( Octothorpe.new(records[3]) )
53
+ m.read.or_die
54
+ end
55
+
56
+
57
+ describe "Model.list" do
58
+ let(:list1) { customer_model_class.list }
59
+
60
+ it "returns an array of customer_model_class records" do
61
+ expect( list1 ).to be_a_kind_of Array
62
+ expect( list1 ).to all(be_a_kind_of customer_model_class)
63
+ end
64
+
65
+ it "returns the data from the interface" do
66
+ expect( list1.size ).to eq records.size
67
+ expect( list1.map(&:to_ot).map(&:to_h) ).to match_array(records)
68
+ end
69
+
70
+ end # of Model.list
71
+
72
+
73
+ describe "#columns" do
74
+
75
+ it "returns the attr_columns list from the class definition" do
76
+ expect( customer_model_class.new.columns ).
77
+ to match_array( [:name,:price,:groups] )
78
+
79
+ end
80
+
81
+ end # of #columns
82
+
83
+
84
+ describe "#set" do
85
+ let (:ot) { records_as_ot[3] }
86
+
87
+ it "takes an Octothorpe or a Hash" do
88
+ expect{ model.set }.to raise_exception ArgumentError
89
+ expect{ model.set(nil) }.to raise_exception ArgumentError
90
+ expect{ model.set(:foo) }.to raise_exception ArgumentError
91
+
92
+ expect{ model.set(ot) }.not_to raise_exception
93
+ end
94
+
95
+ it "returns self" do
96
+ expect( model.set(ot) ).to eq model
97
+ end
98
+
99
+ it "sets the attribute columns from the hash" do
100
+ model.set(ot)
101
+
102
+ expect( model.name ).to eq ot.>>.name
103
+ expect( model.price ).to eq ot.>>.price
104
+ end
105
+
106
+ end # of #set
107
+
108
+
109
+ describe "#to_ot" do
110
+
111
+ it "returns an Octothorpe made of the attribute columns, including the missing ID field" do
112
+ m1 = customer_model_class.new
113
+ expect( m1.to_ot ).to be_a_kind_of Octothorpe
114
+ expect( m1.to_ot.to_h ).to eq( {id: nil, name: nil, price:nil, groups:nil} )
115
+
116
+ m2 = customer_model_class.new(1)
117
+ m2.read
118
+ expect( m2.to_ot ).to be_a_kind_of Octothorpe
119
+ expect( m2.to_ot ).to eq records_as_ot[0]
120
+
121
+ m2 = customer_model_class.new(2)
122
+ m2.read
123
+ expect( m2.to_ot ).to be_a_kind_of Octothorpe
124
+ expect( m2.to_ot ).to eq records_as_ot[1]
125
+ end
126
+
127
+ end # of #to_ot
128
+
129
+
130
+ describe "#map_to_model" do
131
+
132
+ it "sets the columns" do
133
+ cm = customer_model_class.new
134
+ cm.map_to_model(records.last)
135
+
136
+ expect( cm.groups ).to eq "trains,school"
137
+ end
138
+
139
+ end # of #map_to_model
140
+
141
+
142
+ describe "#map_to_interface" do
143
+
144
+ it "returns the columns" do
145
+ cm = customer_model_class.new
146
+ cm.map_to_model(records.last)
147
+
148
+ expect( cm.map_to_interface ).to be_an Octothorpe
149
+ expect( cm.map_to_interface.>>.groups ).to eq( "trains,school" )
150
+ end
151
+
152
+ it "includes the ID field" do
153
+ cm = customer_model_class.new(2).read
154
+
155
+ expect( cm.map_to_interface ).to be_an Octothorpe
156
+ expect( cm.map_to_interface.keys ).to include(:id)
157
+ end
158
+
159
+ end # of #map_to_interface
160
+
161
+
162
+ describe "#create" do
163
+
164
+ it "calls map_to_interface to get record data" do
165
+ m = customer_model_class.new(5)
166
+
167
+ expect( m ).to receive(:map_to_interface).and_call_original
168
+
169
+ m.name = "Lurch"
170
+ m.create
171
+ end
172
+
173
+ end # of #create
174
+
175
+
176
+ describe "#read" do
177
+
178
+ it "calls read on the interface" do
179
+ expect( model.interface ).to receive(:read).with(2).and_call_original
180
+ model.read
181
+ end
182
+
183
+ it "sets the attribute columns using map_to_model" do
184
+ ot = records_as_ot.last
185
+ allow( model.interface ).to receive(:read).and_return( ot )
186
+
187
+ cm = customer_model_class.new(1).read
188
+ expect( cm.name ).to eq ot.>>.name
189
+ expect( cm.price ).to eq ot.>>.price
190
+ expect( cm.groups ).to eq ot.>>.groups
191
+ end
192
+
193
+ context "if the interface.read returns an empty Octothorpe" do
194
+ let(:missing) { customer_model_class.new(99) }
195
+
196
+ it "doesn't throw an exception" do
197
+ expect{ missing.read }.not_to raise_exception
198
+ end
199
+
200
+ it "raises an error alert" do
201
+ expect( missing.read.model_status ).to eq :error
202
+ expect( missing.read.alerts.first.type ).to eq :error
203
+ end
204
+ end
205
+
206
+ end # of #read
207
+
208
+
209
+ describe "#update" do
210
+
211
+ it "raises a Pod4Error if model status is :unknown" do
212
+ expect( model.model_status ).to eq :unknown
213
+ expect{ model.update }.to raise_exception Pod4::Pod4Error
214
+ end
215
+
216
+ it "raises a Pod4Error if model status is :deleted" do
217
+ model2.delete
218
+ expect{ model2.update }.to raise_exception Pod4::Pod4Error
219
+ end
220
+
221
+ it "calls map_to_interface to get record data" do
222
+ expect( model3 ).to receive(:map_to_interface)
223
+ model3.update
224
+ end
225
+
226
+ end # of #update
227
+
228
+
229
+ describe "#delete" do
230
+
231
+ it "raises a Pod4Error if model status is :unknown" do
232
+ expect( model.model_status ).to eq :unknown
233
+ expect{ model.delete }.to raise_exception Pod4::Pod4Error
234
+ end
235
+
236
+ it "raises a Pod4Error if model status is :deleted"do
237
+ model2.delete
238
+ expect{ model2.delete }.to raise_exception Pod4::Pod4Error
239
+ end
240
+
241
+ it "still gives you full access to the data after a delete" do
242
+ model2.delete
243
+
244
+ expect( model2.name ).to eq records_as_ot[2].>>.name
245
+ expect( model2.price ).to eq records_as_ot[2].>>.price
246
+ end
247
+
248
+ it "sets status to :deleted" do
249
+ model2.delete
250
+ expect( model2.model_status ).to eq :deleted
251
+ end
252
+
253
+ end # of #delete
254
+
255
+ end
256
+