sequel 5.15.0 → 5.16.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.
@@ -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)