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
@@ -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
+