sequel 5.15.0 → 5.16.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -510,6 +510,46 @@ describe "NestedAttributes plugin" do
510
510
  ["INSERT INTO albums (artist_id, name) VALUES (1, 'Al')", "INSERT INTO albums (name, artist_id) VALUES ('Al', 1)"])
511
511
  end
512
512
 
513
+ it "should not attempt to validate nested attributes twice for one_to_one associations when creating them" do
514
+ @Artist.nested_attributes :first_album
515
+ validated = []
516
+ @Album.class_eval do
517
+ define_method(:validate) do
518
+ super()
519
+ validated << self
520
+ end
521
+ end
522
+ a = @Artist.new(:name=>'Ar', :first_album_attributes=>{:name=>'Al'})
523
+ @db.sqls.must_equal []
524
+ validated.length.must_equal 0
525
+ a.save
526
+ validated.length.must_equal 1
527
+ check_sql_array("INSERT INTO artists (name) VALUES ('Ar')",
528
+ "UPDATE albums SET artist_id = NULL WHERE (artist_id = 1)",
529
+ "INSERT INTO albums (name, artist_id) VALUES ('Al', 1)")
530
+ end
531
+
532
+ it "should not clear reciprocal association before saving new one_to_one associated object" do
533
+ @Artist.one_to_one :first_album, :clone=>:first_album, :reciprocal=>:artist
534
+ @Artist.nested_attributes :first_album
535
+ assoc = []
536
+ @Album.class_eval do
537
+ define_method(:after_save) do
538
+ super()
539
+ assoc << associations[:artist]
540
+ end
541
+ end
542
+ a = @Artist.new(:name=>'Ar', :first_album_attributes=>{:name=>'Al'})
543
+ @db.sqls.must_equal []
544
+ assoc.must_be_empty
545
+ a.save
546
+ assoc.length.must_equal 1
547
+ assoc.first.must_be_kind_of(@Artist)
548
+ check_sql_array("INSERT INTO artists (name) VALUES ('Ar')",
549
+ "UPDATE albums SET artist_id = NULL WHERE (artist_id = 1)",
550
+ "INSERT INTO albums (name, artist_id) VALUES ('Al', 1)")
551
+ end
552
+
513
553
  it "should not save if nested attribute is not valid and should include nested attribute validation errors in the main object's validation errors" do
514
554
  @Artist.class_eval do
515
555
  def validate
@@ -143,10 +143,10 @@ describe Sequel::Model, "pg_array_associations" do
143
143
  @c1.pg_array_to_many :tags, :clone=>:tags, :primary_key=>Sequel.*(:id, 3), :primary_key_method=>:id3, :key=>:tag3_ids, :key_column=>Sequel.pg_array(:tag_ids)[1..2]
144
144
  @c2.many_to_pg_array :artists, :clone=>:artists, :primary_key=>Sequel.*(:id, 3), :primary_key_method=>:id3, :key=>:tag3_ids, :key_column=>Sequel.pg_array(:tag_ids)[1..2]
145
145
 
146
- @c1.filter(:tags=>@o2).sql.must_equal "SELECT * FROM artists WHERE (artists.tag_ids[1:2] @> ARRAY[6]::integer[])"
146
+ @c1.filter(:tags=>@o2).sql.must_equal "SELECT * FROM artists WHERE ((artists.tag_ids)[1:2] @> ARRAY[6]::integer[])"
147
147
  @c2.filter(:artists=>@o1).sql.must_equal "SELECT * FROM tags WHERE ((tags.id * 3) IN (3, 6, 9))"
148
- @c1.filter(:tags=>@c2.where(:id=>1)).sql.must_equal "SELECT * FROM artists WHERE coalesce((artists.tag_ids[1:2] && (SELECT array_agg((tags.id * 3)) FROM tags WHERE (id = 1))), false)"
149
- @c2.filter(:artists=>@c1.where(:id=>1)).sql.must_equal "SELECT * FROM tags WHERE (EXISTS (SELECT 1 FROM (SELECT artists.tag_ids[1:2] AS key FROM artists WHERE (id = 1)) AS t1 WHERE ((tags.id * 3) = any(key))))"
148
+ @c1.filter(:tags=>@c2.where(:id=>1)).sql.must_equal "SELECT * FROM artists WHERE coalesce(((artists.tag_ids)[1:2] && (SELECT array_agg((tags.id * 3)) FROM tags WHERE (id = 1))), false)"
149
+ @c2.filter(:artists=>@c1.where(:id=>1)).sql.must_equal "SELECT * FROM tags WHERE (EXISTS (SELECT 1 FROM (SELECT (artists.tag_ids)[1:2] AS key FROM artists WHERE (id = 1)) AS t1 WHERE ((tags.id * 3) = any(key))))"
150
150
  end
151
151
 
152
152
  it "should raise an error if associated model does not have a primary key, and :primary_key is not specified" do
@@ -168,7 +168,7 @@ describe Sequel::Model, "pg_array_associations" do
168
168
 
169
169
  it "should support a :key_column option" do
170
170
  @c2.many_to_pg_array :artists, :clone=>:artists, :key_column=>Sequel.pg_array(:tag_ids)[1..2], :key=>:tag2_ids
171
- @o2.artists_dataset.sql.must_equal "SELECT * FROM artists WHERE (artists.tag_ids[1:2] @> ARRAY[2]::integer[])"
171
+ @o2.artists_dataset.sql.must_equal "SELECT * FROM artists WHERE ((artists.tag_ids)[1:2] @> ARRAY[2]::integer[])"
172
172
  end
173
173
 
174
174
  it "should support a :primary_key option" do
@@ -211,7 +211,7 @@ describe Sequel::Model, "pg_array_associations" do
211
211
  @c1.pg_array_to_many :tags, :clone=>:tags, :dataset=>proc{Tag.where(:id=>tag_ids.map{|x| x*2})}
212
212
  @c2.many_to_pg_array :artists, :clone=>:artists, :dataset=>proc{Artist.where(Sequel.pg_array(Sequel.pg_array(:tag_ids)[1..2]).contains([id]))}
213
213
  @o1.tags_dataset.sql.must_equal "SELECT * FROM tags WHERE (id IN (2, 4, 6))"
214
- @o2.artists_dataset.sql.must_equal "SELECT * FROM artists WHERE (tag_ids[1:2] @> ARRAY[2])"
214
+ @o2.artists_dataset.sql.must_equal "SELECT * FROM artists WHERE ((tag_ids)[1:2] @> ARRAY[2])"
215
215
  end
216
216
 
217
217
  it "should support a :limit option" do
@@ -274,7 +274,7 @@ describe Sequel::Model, "pg_array_associations" do
274
274
 
275
275
  a = @c2.eager(:artists).all
276
276
  a.must_equal [@o2]
277
- @db.sqls.must_equal ["SELECT * FROM tags", "SELECT * FROM artists WHERE (artists.tag_ids[1:2] && ARRAY[6]::integer[])"]
277
+ @db.sqls.must_equal ["SELECT * FROM tags", "SELECT * FROM artists WHERE ((artists.tag_ids)[1:2] && ARRAY[6]::integer[])"]
278
278
  a.first.artists.must_equal [@o1]
279
279
  @db.sqls.must_equal []
280
280
  end
@@ -461,13 +461,13 @@ describe Sequel::Model, "pg_array_associations" do
461
461
 
462
462
  a = @c1.eager_graph(:tags).all
463
463
  a.must_equal [@o1]
464
- @db.sqls.must_equal ["SELECT artists.id, artists.tag_ids, tags.id AS tags_id FROM artists LEFT OUTER JOIN tags ON (artists.tag_ids[1:2] @> ARRAY[(tags.id * 3)])"]
464
+ @db.sqls.must_equal ["SELECT artists.id, artists.tag_ids, tags.id AS tags_id FROM artists LEFT OUTER JOIN tags ON ((artists.tag_ids)[1:2] @> ARRAY[(tags.id * 3)])"]
465
465
  a.first.tags.must_equal [@o2]
466
466
  @db.sqls.must_equal []
467
467
 
468
468
  a = @c2.eager_graph(:artists).all
469
469
  a.must_equal [@o2]
470
- @db.sqls.must_equal ["SELECT tags.id, artists.id AS artists_id, artists.tag_ids FROM tags LEFT OUTER JOIN artists ON (artists.tag_ids[1:2] @> ARRAY[tags.id3])"]
470
+ @db.sqls.must_equal ["SELECT tags.id, artists.id AS artists_id, artists.tag_ids FROM tags LEFT OUTER JOIN artists ON ((artists.tag_ids)[1:2] @> ARRAY[tags.id3])"]
471
471
  a.first.artists.must_equal [@o1]
472
472
  @db.sqls.must_equal []
473
473
  end
@@ -17,12 +17,12 @@ describe "Sequel::Postgres::ArrayOp" do
17
17
  end
18
18
 
19
19
  it "#[] should support subscript access" do
20
- @db.literal(@a[1]).must_equal "a[1]"
21
- @db.literal(@a[1][2]).must_equal "a[1][2]"
20
+ @db.literal(@a[1]).must_equal "(a)[1]"
21
+ @db.literal(@a[1][2]).must_equal "(a)[1][2]"
22
22
  end
23
23
 
24
24
  it "#[] with a range should return an ArrayOp" do
25
- @db.literal(@a[1..2].any).must_equal "ANY(a[1:2])"
25
+ @db.literal(@a[1..2].any).must_equal "ANY((a)[1:2])"
26
26
  end
27
27
 
28
28
  it "#any should use the ANY method" do
@@ -52,12 +52,12 @@ describe "Sequel::Postgres::ArrayOp" do
52
52
 
53
53
  it "#remove should remove the element from the array" do
54
54
  @db.literal(@a.remove(1)).must_equal "array_remove(a, 1)"
55
- @db.literal(@a.remove(1)[2]).must_equal "array_remove(a, 1)[2]"
55
+ @db.literal(@a.remove(1)[2]).must_equal "(array_remove(a, 1))[2]"
56
56
  end
57
57
 
58
58
  it "#remove should replace the element in the array with another" do
59
59
  @db.literal(@a.replace(1, 2)).must_equal "array_replace(a, 1, 2)"
60
- @db.literal(@a.replace(1, 2)[3]).must_equal "array_replace(a, 1, 2)[3]"
60
+ @db.literal(@a.replace(1, 2)[3]).must_equal "(array_replace(a, 1, 2))[3]"
61
61
  end
62
62
 
63
63
  it "#unshift should use the || operator in prepend mode" do
@@ -43,7 +43,7 @@ describe "Sequel::Postgres::HStoreOp" do
43
43
  end
44
44
 
45
45
  it "#[] should return a PGArrayOp if given an array" do
46
- @ds.literal(@h[%w'a'][0]).must_equal "(h -> ARRAY['a'])[0]"
46
+ @ds.literal(@h[%w'a'][0]).must_equal "((h -> ARRAY['a']))[0]"
47
47
  end
48
48
 
49
49
  it "#[] should not return a PGArrayOp if given an array but pg_array_op is not supported" do
@@ -58,11 +58,11 @@ describe "Sequel::Postgres::HStoreOp" do
58
58
  end
59
59
 
60
60
  it "#[] should return a PGArrayOp if given a PGArray" do
61
- @ds.literal(@h[Sequel.pg_array(%w'a')][0]).must_equal "(h -> ARRAY['a'])[0]"
61
+ @ds.literal(@h[Sequel.pg_array(%w'a')][0]).must_equal "((h -> ARRAY['a']))[0]"
62
62
  end
63
63
 
64
64
  it "#[] should return a PGArrayOp if given a PGArrayOp" do
65
- @ds.literal(@h[Sequel.pg_array_op(:a)][0]).must_equal "(h -> a)[0]"
65
+ @ds.literal(@h[Sequel.pg_array_op(:a)][0]).must_equal "((h -> a))[0]"
66
66
  end
67
67
 
68
68
  it "#[] should return a string expression" do
@@ -157,8 +157,8 @@ describe "Sequel::Postgres::HStoreOp" do
157
157
  end
158
158
 
159
159
  it "#keys and #akeys should return PGArrayOps" do
160
- @ds.literal(@h.keys[0]).must_equal "akeys(h)[0]"
161
- @ds.literal(@h.akeys[0]).must_equal "akeys(h)[0]"
160
+ @ds.literal(@h.keys[0]).must_equal "(akeys(h))[0]"
161
+ @ds.literal(@h.akeys[0]).must_equal "(akeys(h))[0]"
162
162
  end
163
163
 
164
164
  it "#populate should use the populate_record function" do
@@ -194,7 +194,7 @@ describe "Sequel::Postgres::HStoreOp" do
194
194
  end
195
195
 
196
196
  it "#to_array should return a PGArrayOp" do
197
- @ds.literal(@h.to_array[0]).must_equal "hstore_to_array(h)[0]"
197
+ @ds.literal(@h.to_array[0]).must_equal "(hstore_to_array(h))[0]"
198
198
  end
199
199
 
200
200
  it "#to_matrix should use the hstore_to_matrix function" do
@@ -202,7 +202,7 @@ describe "Sequel::Postgres::HStoreOp" do
202
202
  end
203
203
 
204
204
  it "#to_matrix should return a PGArrayOp" do
205
- @ds.literal(@h.to_matrix[0]).must_equal "hstore_to_matrix(h)[0]"
205
+ @ds.literal(@h.to_matrix[0]).must_equal "(hstore_to_matrix(h))[0]"
206
206
  end
207
207
 
208
208
  it "#values and #avals should use the avals function" do
@@ -211,8 +211,8 @@ describe "Sequel::Postgres::HStoreOp" do
211
211
  end
212
212
 
213
213
  it "#values and #avals should return PGArrayOps" do
214
- @ds.literal(@h.values[0]).must_equal "avals(h)[0]"
215
- @ds.literal(@h.avals[0]).must_equal "avals(h)[0]"
214
+ @ds.literal(@h.values[0]).must_equal "(avals(h))[0]"
215
+ @ds.literal(@h.avals[0]).must_equal "(avals(h))[0]"
216
216
  end
217
217
 
218
218
  it "should have Sequel.hstore_op return HStoreOp instances as-is" do
@@ -18,11 +18,11 @@ describe "Sequel::Postgres::PGRowOp" do
18
18
  end
19
19
 
20
20
  it "#[] should support array access if not given an identifier" do
21
- @db.literal(@a[:b][1]).must_equal "(a).b[1]"
21
+ @db.literal(@a[:b][1]).must_equal "((a).b)[1]"
22
22
  end
23
23
 
24
24
  it "#[] should be chainable with array access" do
25
- @db.literal(@a[1][:b]).must_equal "(a[1]).b"
25
+ @db.literal(@a[1][:b]).must_equal "((a)[1]).b"
26
26
  end
27
27
 
28
28
  it "#splat should return a splatted argument inside parentheses" do
@@ -15,7 +15,7 @@ describe "Sequel sql_expr extension" do
15
15
  @ds.literal(s+1).must_equal "(foo + 1)"
16
16
  @ds.literal(s & true).must_equal "(foo AND 't')"
17
17
  @ds.literal(s < 1).must_equal "(foo < 1)"
18
- @ds.literal(s.sql_subscript(1)).must_equal "foo[1]"
18
+ @ds.literal(s.sql_subscript(1)).must_equal "(foo)[1]"
19
19
  @ds.literal(s.like('a')).must_equal "(foo LIKE 'a' ESCAPE '\\')"
20
20
  @ds.literal(s.as(:a)).must_equal "foo AS a"
21
21
  @ds.literal(s.cast(Integer)).must_equal "CAST(foo AS integer)"
@@ -85,6 +85,6 @@ describe "string_agg extension" do
85
85
  ds.literal(@sa1.cast(:b)).must_equal "CAST(string_agg(c, ',') AS b)"
86
86
  ds.literal(@sa1.desc).must_equal "string_agg(c, ',') DESC"
87
87
  ds.literal(@sa1 =~ /a/).must_equal "(string_agg(c, ',') ~ 'a')"
88
- ds.literal(@sa1.sql_subscript(1)).must_equal "string_agg(c, ',')[1]"
88
+ ds.literal(@sa1.sql_subscript(1)).must_equal "(string_agg(c, ','))[1]"
89
89
  end
90
90
  end
@@ -953,7 +953,7 @@ if DB.dataset.supports_window_functions?
953
953
  must_equal [{:sum=>110, :id=>1}, {:sum=>1100, :id=>2}, {:sum=>11000, :id=>3}, {:sum=>110000, :id=>4}, {:sum=>100000, :id=>5}, {:sum=>nil, :id=>6}]
954
954
  end
955
955
 
956
- cspecify "should give correct results for aggregate window functions with offsets for RANGES", :mssql, [proc{DB.server_version < 110000}, :postgres] do
956
+ cspecify "should give correct results for aggregate window functions with offsets for RANGES", :mssql, :sqlite, [proc{DB.server_version < 110000}, :postgres] do
957
957
  @ds.select(:id){sum(:amount).over(:order=>:group_id, :frame=>{:type=>:range, :start=>1}).as(:sum)}.all.
958
958
  must_equal [{:sum=>111, :id=>1}, {:sum=>111, :id=>2}, {:sum=>111, :id=>3}, {:sum=>111111, :id=>4}, {:sum=>111111, :id=>5}, {:sum=>111111, :id=>6}]
959
959
  @ds.select(:id){sum(:amount).over(:order=>:group_id, :frame=>{:type=>:range, :start=>0, :end=>1}).as(:sum)}.all.
@@ -82,8 +82,207 @@ describe "Database transactions" do
82
82
  end}.must_raise(Interrupt)
83
83
  @d.count.must_equal 0
84
84
  end
85
+
86
+ it "should support rollback_on_exit" do
87
+ @db.transaction do
88
+ @d.insert(:name => 'abc', :value => 1)
89
+ @db.rollback_on_exit
90
+ end
91
+ @d.must_be_empty
92
+
93
+ catch(:foo) do
94
+ @db.transaction do
95
+ @d.insert(:name => 'abc', :value => 1)
96
+ @db.rollback_on_exit
97
+ throw :foo
98
+ end
99
+ end
100
+ @d.must_be_empty
101
+
102
+ lambda do
103
+ @db.transaction do
104
+ @d.insert(:name => 'abc', :value => 1)
105
+ @db.rollback_on_exit
106
+ return true
107
+ end
108
+ end
109
+ @d.must_be_empty
110
+
111
+ @db.transaction do
112
+ @d.insert(:name => 'abc', :value => 1)
113
+ @db.rollback_on_exit
114
+ @db.rollback_on_exit(:cancel=>true)
115
+ end
116
+ @d.count.must_equal 1
117
+
118
+ @d.delete
119
+ @db.transaction do
120
+ @d.insert(:name => 'abc', :value => 1)
121
+ @db.rollback_on_exit(:cancel=>true)
122
+ end
123
+ @d.count.must_equal 1
124
+
125
+ @d.delete
126
+ @db.transaction do
127
+ @d.insert(:name => 'abc', :value => 1)
128
+ @db.rollback_on_exit
129
+ @db.rollback_on_exit(:cancel=>true)
130
+ @db.rollback_on_exit
131
+ end
132
+ @d.must_be_empty
133
+ end
85
134
 
86
135
  if DB.supports_savepoints?
136
+ it "should support rollback_on_exit inside savepoints" do
137
+ @db.transaction do
138
+ @d.insert(:name => 'abc', :value => 1)
139
+ @db.transaction(:savepoint=>true) do
140
+ @d.insert(:name => 'def', :value => 2)
141
+ @db.rollback_on_exit
142
+ end
143
+ end
144
+ @d.must_be_empty
145
+
146
+ @db.transaction do
147
+ @d.insert(:name => 'abc', :value => 1)
148
+ @db.transaction(:savepoint=>true) do
149
+ @d.insert(:name => 'def', :value => 2)
150
+ @db.rollback_on_exit
151
+ @db.transaction(:savepoint=>true) do
152
+ @d.insert(:name => 'ghi', :value => 3)
153
+ end
154
+ end
155
+ end
156
+ @d.must_be_empty
157
+
158
+ @db.transaction do
159
+ @d.insert(:name => 'abc', :value => 1)
160
+ @db.transaction(:savepoint=>true) do
161
+ @d.insert(:name => 'def', :value => 2)
162
+ @db.transaction(:savepoint=>true) do
163
+ @db.rollback_on_exit
164
+ @d.insert(:name => 'ghi', :value => 3)
165
+ end
166
+ end
167
+ end
168
+ @d.must_be_empty
169
+ end
170
+
171
+ it "should support rollback_on_exit with :savepoint option" do
172
+ @db.transaction do
173
+ @d.insert(:name => 'abc', :value => 1)
174
+ @db.transaction(:savepoint=>true) do
175
+ @d.insert(:name => 'def', :value => 2)
176
+ @db.rollback_on_exit(:savepoint=>true)
177
+ end
178
+ end
179
+ @d.select_order_map(:value).must_equal [1]
180
+
181
+ @d.delete
182
+ @db.transaction do
183
+ @d.insert(:name => 'abc', :value => 1)
184
+ @db.transaction(:savepoint=>true) do
185
+ @d.insert(:name => 'def', :value => 2)
186
+ @db.rollback_on_exit(:savepoint=>true)
187
+ @db.transaction(:savepoint=>true) do
188
+ @db.rollback_on_exit(:savepoint=>true)
189
+ @d.insert(:name => 'ghi', :value => 3)
190
+ end
191
+ end
192
+ end
193
+ @d.select_order_map(:value).must_equal [1]
194
+ end
195
+
196
+ it "should support rollback_on_exit with :savepoint=>Integer" do
197
+ @db.transaction do
198
+ @d.insert(:name => 'abc', :value => 1)
199
+ @db.transaction(:savepoint=>true) do
200
+ @d.insert(:name => 'def', :value => 2)
201
+ @db.rollback_on_exit(:savepoint=>2)
202
+ end
203
+ end
204
+ @d.must_be_empty
205
+
206
+ @db.transaction do
207
+ @d.insert(:name => 'abc', :value => 1)
208
+ @db.transaction(:savepoint=>true) do
209
+ @d.insert(:name => 'def', :value => 2)
210
+ @db.rollback_on_exit(:savepoint=>3)
211
+ end
212
+ end
213
+ @d.must_be_empty
214
+
215
+ @db.transaction do
216
+ @d.insert(:name => 'abc', :value => 1)
217
+ @db.transaction(:savepoint=>true) do
218
+ @d.insert(:name => 'def', :value => 2)
219
+ @db.transaction(:savepoint=>true) do
220
+ @db.rollback_on_exit(:savepoint=>2)
221
+ @d.insert(:name => 'ghi', :value => 3)
222
+ end
223
+ end
224
+ end
225
+ @d.select_order_map(:value).must_equal [1]
226
+ end
227
+
228
+ it "should support rollback_on_exit with :savepoint=>Integer and :cancel" do
229
+ @db.transaction do
230
+ @d.insert(:name => 'abc', :value => 1)
231
+ @db.transaction(:savepoint=>true) do
232
+ @db.rollback_on_exit(:savepoint=>true)
233
+ @d.insert(:name => 'def', :value => 2)
234
+ @db.transaction(:savepoint=>true) do
235
+ @db.rollback_on_exit(:savepoint=>2, :cancel=>true)
236
+ @d.insert(:name => 'ghi', :value => 3)
237
+ end
238
+ end
239
+ end
240
+ @d.select_order_map(:value).must_equal [1, 2, 3]
241
+
242
+ @d.delete
243
+ @db.transaction do
244
+ @db.rollback_on_exit(:savepoint=>true)
245
+ @d.insert(:name => 'abc', :value => 1)
246
+ @db.transaction(:savepoint=>true) do
247
+ @db.rollback_on_exit(:savepoint=>true)
248
+ @d.insert(:name => 'def', :value => 2)
249
+ @db.transaction(:savepoint=>true) do
250
+ @db.rollback_on_exit(:savepoint=>3, :cancel=>true)
251
+ @d.insert(:name => 'ghi', :value => 3)
252
+ end
253
+ end
254
+ end
255
+ @d.select_order_map(:value).must_equal [1, 2, 3]
256
+
257
+ @d.delete
258
+ @db.transaction do
259
+ @d.insert(:name => 'abc', :value => 1)
260
+ @db.rollback_on_exit(:savepoint=>true)
261
+ @db.transaction(:savepoint=>true) do
262
+ @d.insert(:name => 'def', :value => 2)
263
+ @db.transaction(:savepoint=>true) do
264
+ @db.rollback_on_exit(:savepoint=>4, :cancel=>true)
265
+ @d.insert(:name => 'ghi', :value => 3)
266
+ end
267
+ end
268
+ end
269
+ @d.select_order_map(:value).must_equal [1, 2, 3]
270
+
271
+ @d.delete
272
+ @db.transaction do
273
+ @d.insert(:name => 'abc', :value => 1)
274
+ @db.transaction(:savepoint=>true) do
275
+ @db.rollback_on_exit(:savepoint=>2)
276
+ @d.insert(:name => 'def', :value => 2)
277
+ @db.transaction(:savepoint=>true) do
278
+ @db.rollback_on_exit(:savepoint=>2, :cancel=>true)
279
+ @d.insert(:name => 'ghi', :value => 3)
280
+ end
281
+ end
282
+ end
283
+ @d.must_be_empty
284
+ end
285
+
87
286
  it "should handle table_exists? failures inside transactions" do
88
287
  @db.transaction do
89
288
  @d.insert(:name => '1')
@@ -1084,6 +1084,37 @@ describe Sequel::Model, "one_to_one" do
1084
1084
  DB.sqls.must_equal []
1085
1085
  end
1086
1086
 
1087
+ it "should have setter not unset reciprocal during save if reciprocal is the same as current" do
1088
+ @c2.many_to_one :parent, :class => @c2, :key=>:parent_id
1089
+ @c2.one_to_one :child, :class => @c2, :key=>:parent_id, :reciprocal=>:parent
1090
+
1091
+ d = @c2.new(:id => 1)
1092
+ e = @c2.new(:id => 2)
1093
+ e2 = @c2.new(:id => 3)
1094
+ e3 = @c2.new(:id => 4)
1095
+ d.associations[:parent] = e
1096
+ e.associations[:child] = d
1097
+ e2.associations[:child] = d
1098
+ e3.associations[:child] = e
1099
+ assoc = nil
1100
+ d.define_singleton_method(:after_save) do
1101
+ super()
1102
+ assoc = associations
1103
+ end
1104
+
1105
+ def e.set_associated_object_if_same?; true; end
1106
+ e.child = d
1107
+ assoc.must_equal(:parent=>e)
1108
+
1109
+ def e2.set_associated_object_if_same?; true; end
1110
+ e2.child = e
1111
+ assoc.must_equal(:parent=>nil)
1112
+
1113
+ d.associations.clear
1114
+ e3.child = d
1115
+ assoc.must_equal({})
1116
+ end
1117
+
1087
1118
  it "should not add associations methods directly to class" do
1088
1119
  @c2.one_to_one :parent, :class => @c2
1089
1120
  @c2.instance_methods.must_include(:parent)