sequel 3.28.0 → 3.29.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (148) hide show
  1. data/CHANGELOG +119 -3
  2. data/Rakefile +5 -3
  3. data/bin/sequel +1 -5
  4. data/doc/model_hooks.rdoc +9 -1
  5. data/doc/opening_databases.rdoc +49 -40
  6. data/doc/prepared_statements.rdoc +27 -6
  7. data/doc/release_notes/3.28.0.txt +2 -2
  8. data/doc/release_notes/3.29.0.txt +459 -0
  9. data/doc/sharding.rdoc +7 -1
  10. data/doc/testing.rdoc +18 -9
  11. data/doc/transactions.rdoc +41 -1
  12. data/lib/sequel/adapters/ado.rb +28 -17
  13. data/lib/sequel/adapters/ado/mssql.rb +18 -6
  14. data/lib/sequel/adapters/amalgalite.rb +11 -7
  15. data/lib/sequel/adapters/db2.rb +122 -70
  16. data/lib/sequel/adapters/dbi.rb +15 -15
  17. data/lib/sequel/adapters/do.rb +5 -36
  18. data/lib/sequel/adapters/do/mysql.rb +0 -5
  19. data/lib/sequel/adapters/do/postgres.rb +0 -5
  20. data/lib/sequel/adapters/do/sqlite.rb +0 -5
  21. data/lib/sequel/adapters/firebird.rb +3 -6
  22. data/lib/sequel/adapters/ibmdb.rb +24 -16
  23. data/lib/sequel/adapters/informix.rb +2 -4
  24. data/lib/sequel/adapters/jdbc.rb +47 -11
  25. data/lib/sequel/adapters/jdbc/as400.rb +5 -24
  26. data/lib/sequel/adapters/jdbc/db2.rb +0 -5
  27. data/lib/sequel/adapters/jdbc/derby.rb +217 -0
  28. data/lib/sequel/adapters/jdbc/firebird.rb +0 -5
  29. data/lib/sequel/adapters/jdbc/h2.rb +10 -12
  30. data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
  31. data/lib/sequel/adapters/jdbc/informix.rb +0 -5
  32. data/lib/sequel/adapters/jdbc/jtds.rb +0 -5
  33. data/lib/sequel/adapters/jdbc/mysql.rb +0 -10
  34. data/lib/sequel/adapters/jdbc/oracle.rb +70 -3
  35. data/lib/sequel/adapters/jdbc/postgresql.rb +0 -11
  36. data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
  37. data/lib/sequel/adapters/jdbc/sqlserver.rb +0 -5
  38. data/lib/sequel/adapters/jdbc/transactions.rb +56 -7
  39. data/lib/sequel/adapters/mock.rb +315 -0
  40. data/lib/sequel/adapters/mysql.rb +64 -51
  41. data/lib/sequel/adapters/mysql2.rb +15 -9
  42. data/lib/sequel/adapters/odbc.rb +13 -6
  43. data/lib/sequel/adapters/odbc/db2.rb +0 -4
  44. data/lib/sequel/adapters/odbc/mssql.rb +0 -5
  45. data/lib/sequel/adapters/openbase.rb +2 -4
  46. data/lib/sequel/adapters/oracle.rb +333 -51
  47. data/lib/sequel/adapters/postgres.rb +80 -27
  48. data/lib/sequel/adapters/shared/access.rb +0 -6
  49. data/lib/sequel/adapters/shared/db2.rb +13 -15
  50. data/lib/sequel/adapters/shared/firebird.rb +6 -6
  51. data/lib/sequel/adapters/shared/mssql.rb +23 -18
  52. data/lib/sequel/adapters/shared/mysql.rb +6 -6
  53. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
  54. data/lib/sequel/adapters/shared/oracle.rb +185 -30
  55. data/lib/sequel/adapters/shared/postgres.rb +35 -18
  56. data/lib/sequel/adapters/shared/progress.rb +0 -6
  57. data/lib/sequel/adapters/shared/sqlite.rb +116 -37
  58. data/lib/sequel/adapters/sqlite.rb +16 -8
  59. data/lib/sequel/adapters/swift.rb +5 -5
  60. data/lib/sequel/adapters/swift/mysql.rb +0 -5
  61. data/lib/sequel/adapters/swift/postgres.rb +0 -5
  62. data/lib/sequel/adapters/swift/sqlite.rb +6 -4
  63. data/lib/sequel/adapters/tinytds.rb +13 -10
  64. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +8 -0
  65. data/lib/sequel/core.rb +40 -0
  66. data/lib/sequel/database/connecting.rb +1 -2
  67. data/lib/sequel/database/dataset.rb +3 -3
  68. data/lib/sequel/database/dataset_defaults.rb +58 -0
  69. data/lib/sequel/database/misc.rb +62 -2
  70. data/lib/sequel/database/query.rb +113 -49
  71. data/lib/sequel/database/schema_methods.rb +7 -2
  72. data/lib/sequel/dataset/actions.rb +37 -19
  73. data/lib/sequel/dataset/features.rb +24 -0
  74. data/lib/sequel/dataset/graph.rb +7 -6
  75. data/lib/sequel/dataset/misc.rb +11 -3
  76. data/lib/sequel/dataset/mutation.rb +2 -3
  77. data/lib/sequel/dataset/prepared_statements.rb +6 -4
  78. data/lib/sequel/dataset/query.rb +46 -15
  79. data/lib/sequel/dataset/sql.rb +28 -4
  80. data/lib/sequel/extensions/named_timezones.rb +5 -0
  81. data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
  82. data/lib/sequel/model.rb +2 -1
  83. data/lib/sequel/model/associations.rb +115 -33
  84. data/lib/sequel/model/base.rb +91 -31
  85. data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
  86. data/lib/sequel/plugins/dataset_associations.rb +100 -0
  87. data/lib/sequel/plugins/force_encoding.rb +6 -6
  88. data/lib/sequel/plugins/identity_map.rb +1 -1
  89. data/lib/sequel/plugins/many_through_many.rb +6 -10
  90. data/lib/sequel/plugins/prepared_statements.rb +12 -1
  91. data/lib/sequel/plugins/prepared_statements_associations.rb +1 -1
  92. data/lib/sequel/plugins/rcte_tree.rb +29 -15
  93. data/lib/sequel/plugins/serialization.rb +6 -1
  94. data/lib/sequel/plugins/sharding.rb +0 -5
  95. data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
  96. data/lib/sequel/plugins/typecast_on_load.rb +9 -12
  97. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  98. data/lib/sequel/timezones.rb +42 -42
  99. data/lib/sequel/version.rb +1 -1
  100. data/spec/adapters/mssql_spec.rb +29 -29
  101. data/spec/adapters/mysql_spec.rb +86 -104
  102. data/spec/adapters/oracle_spec.rb +48 -76
  103. data/spec/adapters/postgres_spec.rb +98 -33
  104. data/spec/adapters/spec_helper.rb +0 -5
  105. data/spec/adapters/sqlite_spec.rb +24 -21
  106. data/spec/core/connection_pool_spec.rb +9 -15
  107. data/spec/core/core_sql_spec.rb +20 -31
  108. data/spec/core/database_spec.rb +491 -227
  109. data/spec/core/dataset_spec.rb +638 -1051
  110. data/spec/core/expression_filters_spec.rb +0 -1
  111. data/spec/core/mock_adapter_spec.rb +378 -0
  112. data/spec/core/object_graph_spec.rb +48 -114
  113. data/spec/core/schema_generator_spec.rb +3 -3
  114. data/spec/core/schema_spec.rb +51 -114
  115. data/spec/core/spec_helper.rb +3 -90
  116. data/spec/extensions/class_table_inheritance_spec.rb +1 -1
  117. data/spec/extensions/dataset_associations_spec.rb +199 -0
  118. data/spec/extensions/instance_hooks_spec.rb +71 -0
  119. data/spec/extensions/named_timezones_spec.rb +22 -2
  120. data/spec/extensions/nested_attributes_spec.rb +3 -0
  121. data/spec/extensions/schema_spec.rb +1 -1
  122. data/spec/extensions/serialization_modification_detection_spec.rb +1 -0
  123. data/spec/extensions/serialization_spec.rb +5 -8
  124. data/spec/extensions/spec_helper.rb +4 -0
  125. data/spec/extensions/thread_local_timezones_spec.rb +22 -2
  126. data/spec/extensions/typecast_on_load_spec.rb +1 -6
  127. data/spec/integration/associations_test.rb +123 -12
  128. data/spec/integration/dataset_test.rb +140 -47
  129. data/spec/integration/eager_loader_test.rb +19 -21
  130. data/spec/integration/model_test.rb +80 -1
  131. data/spec/integration/plugin_test.rb +179 -128
  132. data/spec/integration/prepared_statement_test.rb +92 -91
  133. data/spec/integration/schema_test.rb +42 -23
  134. data/spec/integration/spec_helper.rb +25 -31
  135. data/spec/integration/timezone_test.rb +38 -12
  136. data/spec/integration/transaction_test.rb +161 -34
  137. data/spec/integration/type_test.rb +3 -3
  138. data/spec/model/association_reflection_spec.rb +83 -7
  139. data/spec/model/associations_spec.rb +393 -676
  140. data/spec/model/base_spec.rb +186 -116
  141. data/spec/model/dataset_methods_spec.rb +7 -27
  142. data/spec/model/eager_loading_spec.rb +343 -867
  143. data/spec/model/hooks_spec.rb +160 -79
  144. data/spec/model/model_spec.rb +118 -165
  145. data/spec/model/plugins_spec.rb +7 -13
  146. data/spec/model/record_spec.rb +138 -207
  147. data/spec/model/spec_helper.rb +10 -73
  148. metadata +14 -8
@@ -1,55 +1,47 @@
1
1
  require File.join(File.dirname(File.expand_path(__FILE__)), "spec_helper")
2
2
 
3
3
  describe "Model#after_initialize" do
4
- specify "should be called after initialization" do
5
- $values1 = nil
6
- $reached_after_initialized = false
4
+ specify "should be called after initialization for both new object and objects retrieved from the database" do
7
5
 
8
- a = Class.new(Sequel::Model)
9
- a.class_eval do
6
+ a = Class.new(Sequel::Model) do
7
+ self::Foo = []
10
8
  columns :x, :y
11
9
  def after_initialize
12
- $values1 = @values.clone
13
- $reached_after_initialized = true
10
+ model::Foo << @values.clone
14
11
  end
15
12
  end
16
13
 
17
14
  a.new(:x => 1, :y => 2)
18
- $values1.should == {:x => 1, :y => 2}
19
- $reached_after_initialized.should == true
15
+ a.call(:x => 1, :y => 3)
16
+ a::Foo.should == [{:x => 1, :y => 2}, {:x => 1, :y => 3}]
20
17
  end
21
18
  end
22
19
 
23
20
  describe "Model#before_create && Model#after_create" do
24
21
  before do
25
- MODEL_DB.reset
26
-
27
- @c = Class.new(Sequel::Model(:items))
28
- @c.class_eval do
22
+ @c = Class.new(Sequel::Model(:items)) do
29
23
  columns :x
30
- no_primary_key
24
+ set_primary_key :x
25
+ unrestrict_primary_key
31
26
 
32
27
  def after_create
33
28
  MODEL_DB << "BLAH after"
34
29
  end
35
30
  end
31
+ MODEL_DB.reset
36
32
  end
37
33
 
38
34
  specify "should be called around new record creation" do
39
35
  @c.send(:define_method, :before_create){MODEL_DB << "BLAH before"}
40
36
  @c.create(:x => 2)
41
- MODEL_DB.sqls.should == [
42
- 'BLAH before',
43
- 'INSERT INTO items (x) VALUES (2)',
44
- 'BLAH after'
45
- ]
37
+ MODEL_DB.sqls.should == ['BLAH before', 'INSERT INTO items (x) VALUES (2)', 'BLAH after', 'SELECT * FROM items WHERE (x = 2) LIMIT 1']
46
38
  end
47
39
 
48
40
  specify ".create should cancel the save and raise an error if before_create returns false and raise_on_save_failure is true" do
49
41
  @c.send(:define_method, :before_create){false}
50
- proc{@c.load(:id => 2233).save}.should_not raise_error(Sequel::ValidationFailed)
51
42
  proc{@c.create(:x => 2)}.should raise_error(Sequel::BeforeHookFailed)
52
43
  MODEL_DB.sqls.should == []
44
+ proc{@c.load(:id => 2233).save}.should_not raise_error(Sequel::ValidationFailed)
53
45
  end
54
46
 
55
47
  specify ".create should cancel the save and return nil if before_create returns false and raise_on_save_failure is false" do
@@ -62,24 +54,20 @@ end
62
54
 
63
55
  describe "Model#before_update && Model#after_update" do
64
56
  before do
65
- MODEL_DB.reset
66
-
67
- @c = Class.new(Sequel::Model(:items))
68
- @c.class_eval do
57
+ @c = Class.new(Sequel::Model(:items)) do
69
58
  columns :id, :x
70
- def after_update; MODEL_DB << "BLAH after" end
59
+ def after_update
60
+ MODEL_DB << "BLAH after"
61
+ end
71
62
  end
63
+ MODEL_DB.reset
72
64
  end
73
65
 
74
66
  specify "should be called around record update" do
75
67
  @c.send(:define_method, :before_update){MODEL_DB << "BLAH before"}
76
68
  m = @c.load(:id => 2233, :x=>123)
77
69
  m.save
78
- MODEL_DB.sqls.should == [
79
- 'BLAH before',
80
- 'UPDATE items SET x = 123 WHERE (id = 2233)',
81
- 'BLAH after'
82
- ]
70
+ MODEL_DB.sqls.should == ['BLAH before', 'UPDATE items SET x = 123 WHERE (id = 2233)', 'BLAH after']
83
71
  end
84
72
 
85
73
  specify "#save should cancel the save and raise an error if before_update returns false and raise_on_save_failure is true" do
@@ -107,35 +95,28 @@ end
107
95
 
108
96
  describe "Model#before_save && Model#after_save" do
109
97
  before do
110
- MODEL_DB.reset
111
-
112
- @c = Class.new(Sequel::Model(:items))
113
- @c.class_eval do
98
+ @c = Class.new(Sequel::Model(:items)) do
114
99
  columns :x
115
- def after_save; MODEL_DB << "BLAH after" end
100
+ def after_save
101
+ MODEL_DB << "BLAH after"
102
+ end
116
103
  end
104
+ MODEL_DB.reset
117
105
  end
118
106
 
119
107
  specify "should be called around record update" do
120
108
  @c.send(:define_method, :before_save){MODEL_DB << "BLAH before"}
121
109
  m = @c.load(:id => 2233, :x=>123)
122
110
  m.save
123
- MODEL_DB.sqls.should == [
124
- 'BLAH before',
125
- 'UPDATE items SET x = 123 WHERE (id = 2233)',
126
- 'BLAH after'
127
- ]
111
+ MODEL_DB.sqls.should == ['BLAH before', 'UPDATE items SET x = 123 WHERE (id = 2233)', 'BLAH after']
128
112
  end
129
113
 
130
114
  specify "should be called around record creation" do
131
115
  @c.send(:define_method, :before_save){MODEL_DB << "BLAH before"}
132
- @c.no_primary_key
116
+ @c.set_primary_key :x
117
+ @c.unrestrict_primary_key
133
118
  @c.create(:x => 2)
134
- MODEL_DB.sqls.should == [
135
- 'BLAH before',
136
- 'INSERT INTO items (x) VALUES (2)',
137
- 'BLAH after'
138
- ]
119
+ MODEL_DB.sqls.should == ['BLAH before', 'INSERT INTO items (x) VALUES (2)', 'BLAH after', 'SELECT * FROM items WHERE (x = 2) LIMIT 1']
139
120
  end
140
121
 
141
122
  specify "#save should cancel the save and raise an error if before_save returns false and raise_on_save_failure is true" do
@@ -163,27 +144,19 @@ end
163
144
 
164
145
  describe "Model#before_destroy && Model#after_destroy" do
165
146
  before do
166
- MODEL_DB.reset
167
-
168
- @c = Class.new(Sequel::Model(:items))
169
- @c.class_eval do
170
- def after_destroy; MODEL_DB << "BLAH after"; end
171
-
172
- def delete
173
- MODEL_DB << "DELETE BLAH"
147
+ @c = Class.new(Sequel::Model(:items)) do
148
+ def after_destroy
149
+ MODEL_DB << "BLAH after"
174
150
  end
175
151
  end
152
+ MODEL_DB.reset
176
153
  end
177
154
 
178
155
  specify "should be called around record destruction" do
179
156
  @c.send(:define_method, :before_destroy){MODEL_DB << "BLAH before"}
180
157
  m = @c.load(:id => 2233)
181
158
  m.destroy
182
- MODEL_DB.sqls.should == [
183
- 'BLAH before',
184
- 'DELETE BLAH',
185
- 'BLAH after'
186
- ]
159
+ MODEL_DB.sqls.should == ['BLAH before', 'DELETE FROM items WHERE (id = 2233)', 'BLAH after']
187
160
  end
188
161
 
189
162
  specify "#destroy should cancel the destroy and raise an error if before_destroy returns false and raise_on_save_failure is true" do
@@ -209,16 +182,15 @@ end
209
182
 
210
183
  describe "Model#before_validation && Model#after_validation" do
211
184
  before do
212
- MODEL_DB.reset
213
-
214
- @c = Class.new(Sequel::Model(:items))
215
- @c.class_eval do
216
- def after_validation; MODEL_DB << "BLAH after" end
185
+ @c = Class.new(Sequel::Model(:items)) do
186
+ columns :id
187
+ def after_validation
188
+ MODEL_DB << "BLAH after"
189
+ end
217
190
 
218
191
  def validate
219
192
  errors.add(:id, 'not valid') unless id == 2233
220
193
  end
221
- columns :id
222
194
  end
223
195
  end
224
196
 
@@ -228,7 +200,6 @@ describe "Model#before_validation && Model#after_validation" do
228
200
  m.should be_valid
229
201
  MODEL_DB.sqls.should == ['BLAH before', 'BLAH after']
230
202
 
231
- MODEL_DB.sqls.clear
232
203
  m = @c.load(:id => 22)
233
204
  m.should_not be_valid
234
205
  MODEL_DB.sqls.should == ['BLAH before', 'BLAH after']
@@ -240,7 +211,6 @@ describe "Model#before_validation && Model#after_validation" do
240
211
  m.save.should == m
241
212
  MODEL_DB.sqls.should == ['BLAH before', 'BLAH after', 'UPDATE items SET x = 123 WHERE (id = 2233)']
242
213
 
243
- MODEL_DB.sqls.clear
244
214
  m = @c.load(:id => 22)
245
215
  m.raise_on_save_failure = false
246
216
  m.save.should == nil
@@ -277,13 +247,10 @@ end
277
247
 
278
248
  describe "Model around filters" do
279
249
  before do
280
- MODEL_DB.reset
281
-
282
- @c = Class.new(Sequel::Model(:items))
283
- @c.class_eval do
250
+ @c = Class.new(Sequel::Model(:items)) do
284
251
  columns :id, :x
285
- def _save_refresh(*a) end
286
252
  end
253
+ MODEL_DB.reset
287
254
  end
288
255
 
289
256
  specify "around_create should be called around new record creation" do
@@ -295,7 +262,7 @@ describe "Model around filters" do
295
262
  end
296
263
  end
297
264
  @c.create(:x => 2)
298
- MODEL_DB.sqls.should == [ 'ac_before', 'INSERT INTO items (x) VALUES (2)', 'ac_after' ]
265
+ MODEL_DB.sqls.should == ['ac_before', 'INSERT INTO items (x) VALUES (2)', 'ac_after', "SELECT * FROM items WHERE (id = 10) LIMIT 1"]
299
266
  end
300
267
 
301
268
  specify "around_delete should be called around record destruction" do
@@ -307,7 +274,7 @@ describe "Model around filters" do
307
274
  end
308
275
  end
309
276
  @c.load(:id=>1, :x => 2).destroy
310
- MODEL_DB.sqls.should == [ 'ad_before', 'DELETE FROM items WHERE (id = 1)', 'ad_after' ]
277
+ MODEL_DB.sqls.should == ['ad_before', 'DELETE FROM items WHERE (id = 1)', 'ad_after']
311
278
  end
312
279
 
313
280
  specify "around_update should be called around updating existing records" do
@@ -319,10 +286,10 @@ describe "Model around filters" do
319
286
  end
320
287
  end
321
288
  @c.load(:id=>1, :x => 2).save
322
- MODEL_DB.sqls.should == [ 'au_before', 'UPDATE items SET x = 2 WHERE (id = 1)', 'au_after' ]
289
+ MODEL_DB.sqls.should == ['au_before', 'UPDATE items SET x = 2 WHERE (id = 1)', 'au_after']
323
290
  end
324
291
 
325
- specify "around_update should be called around saving both new and existing records, around either after_create and after_update" do
292
+ specify "around_save should be called around saving both new and existing records, around either after_create and after_update" do
326
293
  @c.class_eval do
327
294
  def around_update
328
295
  MODEL_DB << 'au_before'
@@ -341,10 +308,9 @@ describe "Model around filters" do
341
308
  end
342
309
  end
343
310
  @c.create(:x => 2)
344
- MODEL_DB.sqls.should == [ 'as_before', 'ac_before', 'INSERT INTO items (x) VALUES (2)', 'ac_after', 'as_after' ]
345
- MODEL_DB.sqls.clear
311
+ MODEL_DB.sqls.should == ['as_before', 'ac_before', 'INSERT INTO items (x) VALUES (2)', 'ac_after', 'as_after', "SELECT * FROM items WHERE (id = 10) LIMIT 1"]
346
312
  @c.load(:id=>1, :x => 2).save
347
- MODEL_DB.sqls.should == [ 'as_before', 'au_before', 'UPDATE items SET x = 2 WHERE (id = 1)', 'au_after', 'as_after' ]
313
+ MODEL_DB.sqls.should == ['as_before', 'au_before', 'UPDATE items SET x = 2 WHERE (id = 1)', 'au_after', 'as_after']
348
314
  end
349
315
 
350
316
  specify "around_validation should be called around validating records" do
@@ -428,5 +394,120 @@ describe "Model around filters" do
428
394
  o.meta_def(:around_validation){}
429
395
  o.save.should == nil
430
396
  end
397
+ end
398
+
399
+ describe "Model#after_commit and #after_rollback" do
400
+ before do
401
+ @db = Sequel.mock(:servers=>{:test=>{}})
402
+ @m = Class.new(Sequel::Model(@db[:items])) do
403
+ attr_accessor :rb
404
+ def _delete
405
+ end
406
+ def after_save
407
+ db.execute('as')
408
+ raise Sequel::Rollback if rb
409
+ end
410
+ def after_commit
411
+ db.execute('ac')
412
+ end
413
+ def after_rollback
414
+ db.execute('ar')
415
+ end
416
+ def after_destroy
417
+ db.execute('ad')
418
+ raise Sequel::Rollback if rb
419
+ end
420
+ def after_destroy_commit
421
+ db.execute('adc')
422
+ end
423
+ def after_destroy_rollback
424
+ db.execute('adr')
425
+ end
426
+ end
427
+ @m.use_transactions = true
428
+ @o = @m.load({})
429
+ @db.sqls
430
+ end
431
+
432
+ specify "should call after_commit for save after the transaction commits if it commits" do
433
+ @o.save
434
+ @db.sqls.should == ['BEGIN', 'as', 'COMMIT', 'ac']
435
+ end
431
436
 
437
+ specify "should call after_rollback for save after the transaction rolls back if it rolls back" do
438
+ @o.rb = true
439
+ @o.save
440
+ @db.sqls.should == ['BEGIN', 'as', 'ROLLBACK', 'ar']
441
+ end
442
+
443
+ specify "should have after_commit respect any surrounding transactions" do
444
+ @db.transaction do
445
+ @o.save
446
+ end
447
+ @db.sqls.should == ['BEGIN', 'as', 'COMMIT', 'ac']
448
+ end
449
+
450
+ specify "should have after_rollback respect any surrounding transactions" do
451
+ @db.transaction do
452
+ @o.rb = true
453
+ @o.save
454
+ end
455
+ @db.sqls.should == ['BEGIN', 'as', 'ROLLBACK', 'ar']
456
+ end
457
+
458
+ specify "should have after_commit work with surrounding transactions and sharding" do
459
+ @db.transaction(:server=>:test) do
460
+ @o.save
461
+ end
462
+ @db.sqls.should == ['BEGIN -- test', 'BEGIN', 'as', 'COMMIT', 'ac', 'COMMIT -- test']
463
+ end
464
+
465
+ specify "should have after_rollback work with surrounding transactions and sharding" do
466
+ @db.transaction(:server=>:test) do
467
+ @o.rb = true
468
+ @o.save
469
+ end
470
+ @db.sqls.should == ['BEGIN -- test', 'BEGIN', 'as', 'ROLLBACK', 'ar', 'COMMIT -- test']
471
+ end
472
+
473
+ specify "should call after_destroy_commit for destroy after the transaction commits if it commits" do
474
+ @o.destroy
475
+ @db.sqls.should == ['BEGIN', 'ad', 'COMMIT', 'adc']
476
+ end
477
+
478
+ specify "should call after_destroy_rollback for destroy after the transaction rolls back if it rolls back" do
479
+ @o.rb = true
480
+ @o.destroy
481
+ @db.sqls.should == ['BEGIN', 'ad', 'ROLLBACK', 'adr']
482
+ end
483
+
484
+ specify "should have after_destroy_commit respect any surrounding transactions" do
485
+ @db.transaction do
486
+ @o.destroy
487
+ end
488
+ @db.sqls.should == ['BEGIN', 'ad', 'COMMIT', 'adc']
489
+ end
490
+
491
+ specify "should have after_destroy_rollback respect any surrounding transactions" do
492
+ @db.transaction do
493
+ @o.rb = true
494
+ @o.destroy
495
+ end
496
+ @db.sqls.should == ['BEGIN', 'ad', 'ROLLBACK', 'adr']
497
+ end
498
+
499
+ specify "should have after_destroy commit work with surrounding transactions and sharding" do
500
+ @db.transaction(:server=>:test) do
501
+ @o.destroy
502
+ end
503
+ @db.sqls.should == ['BEGIN -- test', 'BEGIN', 'ad', 'COMMIT', 'adc', 'COMMIT -- test']
504
+ end
505
+
506
+ specify "should have after_destroy_rollback work with surrounding transactions and sharding" do
507
+ @db.transaction(:server=>:test) do
508
+ @o.rb = true
509
+ @o.destroy
510
+ end
511
+ @db.sqls.should == ['BEGIN -- test', 'BEGIN', 'ad', 'ROLLBACK', 'adr', 'COMMIT -- test']
512
+ end
432
513
  end
@@ -99,6 +99,13 @@ describe "Sequel::Model()" do
99
99
  end.should_not raise_error
100
100
  end
101
101
 
102
+ it "should work without raising an exception with an LiteralString" do
103
+ proc do
104
+ class ::Album < Sequel::Model('table'.lit); end
105
+ class ::Album < Sequel::Model('table'.lit); end
106
+ end.should_not raise_error
107
+ end
108
+
102
109
  it "should work without raising an exception with a database" do
103
110
  proc do
104
111
  class ::Album < Sequel::Model(@db); end
@@ -133,17 +140,16 @@ describe Sequel::Model do
133
140
  it "should be associated with a dataset" do
134
141
  model_a = Class.new(Sequel::Model) { set_dataset MODEL_DB[:as] }
135
142
 
136
- model_a.dataset.should be_a_kind_of(MockDataset)
143
+ model_a.dataset.should be_a_kind_of(Sequel::Mock::Dataset)
137
144
  model_a.dataset.opts[:from].should == [:as]
138
145
 
139
146
  model_b = Class.new(Sequel::Model) { set_dataset MODEL_DB[:bs] }
140
147
 
141
- model_b.dataset.should be_a_kind_of(MockDataset)
148
+ model_b.dataset.should be_a_kind_of(Sequel::Mock::Dataset)
142
149
  model_b.dataset.opts[:from].should == [:bs]
143
150
 
144
151
  model_a.dataset.opts[:from].should == [:as]
145
152
  end
146
-
147
153
  end
148
154
 
149
155
  describe Sequel::Model, "dataset & schema" do
@@ -217,7 +223,7 @@ describe Sequel::Model, "dataset & schema" do
217
223
 
218
224
  it "should raise an error on set_dataset if there is an error connecting to the database" do
219
225
  @model.meta_def(:columns){raise Sequel::DatabaseConnectionError}
220
- proc{@model.set_dataset(MODEL_DB[:foo].join(:blah))}.should raise_error
226
+ proc{@model.set_dataset(Sequel::Database.new[:foo].join(:blah))}.should raise_error
221
227
  end
222
228
 
223
229
  it "should not raise an error if there is a problem getting the columns for a dataset" do
@@ -236,9 +242,8 @@ describe Sequel::Model, "dataset & schema" do
236
242
  end
237
243
  end
238
244
 
239
- describe Sequel::Model, "constructor" do
240
-
241
- before(:each) do
245
+ describe Sequel::Model, "constructors" do
246
+ before do
242
247
  @m = Class.new(Sequel::Model)
243
248
  @m.columns :a, :b
244
249
  end
@@ -257,11 +262,38 @@ describe Sequel::Model, "constructor" do
257
262
  m.values[:a].should == 1
258
263
  end
259
264
 
265
+ it "should have dataset row_proc create an existing object" do
266
+ @m.dataset = Sequel::Dataset.new(nil)
267
+ o = @m.dataset.row_proc.call(:a=>1)
268
+ o.should be_a_kind_of(@m)
269
+ o.values.should == {:a=>1}
270
+ o.new?.should be_false
271
+ end
272
+
273
+ it "should have .call create an existing object" do
274
+ o = @m.call(:a=>1)
275
+ o.should be_a_kind_of(@m)
276
+ o.values.should == {:a=>1}
277
+ o.new?.should be_false
278
+ end
279
+
280
+ it "should have .load create an existing object" do
281
+ o = @m.load(:a=>1)
282
+ o.should be_a_kind_of(@m)
283
+ o.values.should == {:a=>1}
284
+ o.new?.should be_false
285
+ end
286
+
287
+ it "should have .new with a second true argument create an existing object" do
288
+ o = @m.new({:a=>1}, true)
289
+ o.should be_a_kind_of(@m)
290
+ o.values.should == {:a=>1}
291
+ o.new?.should be_false
292
+ end
260
293
  end
261
294
 
262
295
  describe Sequel::Model, "new" do
263
-
264
- before(:each) do
296
+ before do
265
297
  @m = Class.new(Sequel::Model) do
266
298
  set_dataset MODEL_DB[:items]
267
299
  columns :x, :id
@@ -281,15 +313,8 @@ describe Sequel::Model, "new" do
281
313
  end
282
314
 
283
315
  it "should use the last inserted id as primary key if not in values" do
284
- d = @m.dataset
285
- def d.insert(*args)
286
- super
287
- 1234
288
- end
289
-
290
- def d.first
291
- {:x => 1, :id => 1234}
292
- end
316
+ @m.dataset._fetch = {:x => 1, :id => 1234}
317
+ @m.dataset.autoid = 1234
293
318
 
294
319
  o = @m.new(:x => 1)
295
320
  o.save
@@ -299,14 +324,12 @@ describe Sequel::Model, "new" do
299
324
  o.save
300
325
  o.id.should == 333
301
326
  end
302
-
303
327
  end
304
328
 
305
329
  describe Sequel::Model, ".subset" do
306
330
  before do
307
- MODEL_DB.reset
308
-
309
331
  @c = Class.new(Sequel::Model(:items))
332
+ MODEL_DB.reset
310
333
  end
311
334
 
312
335
  specify "should create a filter on the underlying dataset" do
@@ -334,44 +357,31 @@ describe Sequel::Model, ".subset" do
334
357
  end
335
358
 
336
359
  describe Sequel::Model, ".find" do
337
-
338
- before(:each) do
339
- MODEL_DB.reset
340
-
360
+ before do
341
361
  @c = Class.new(Sequel::Model(:items))
342
-
343
- $cache_dataset_row = {:name => 'sharon', :id => 1}
344
- @dataset = @c.dataset
345
- $sqls = []
346
- @dataset.extend(Module.new {
347
- def fetch_rows(sql)
348
- $sqls << sql
349
- yield $cache_dataset_row
350
- end
351
- })
362
+ @c.dataset._fetch = {:name => 'sharon', :id => 1}
363
+ MODEL_DB.reset
352
364
  end
353
365
 
354
366
  it "should return the first record matching the given filter" do
355
367
  @c.find(:name => 'sharon').should be_a_kind_of(@c)
356
- $sqls.last.should == "SELECT * FROM items WHERE (name = 'sharon') LIMIT 1"
368
+ MODEL_DB.sqls.should == ["SELECT * FROM items WHERE (name = 'sharon') LIMIT 1"]
357
369
 
358
370
  @c.find(:name.like('abc%')).should be_a_kind_of(@c)
359
- $sqls.last.should == "SELECT * FROM items WHERE (name LIKE 'abc%') LIMIT 1"
371
+ MODEL_DB.sqls.should == ["SELECT * FROM items WHERE (name LIKE 'abc%') LIMIT 1"]
360
372
  end
361
373
 
362
374
  specify "should accept filter blocks" do
363
375
  @c.find{:id.sql_number > 1}.should be_a_kind_of(@c)
364
- $sqls.last.should == "SELECT * FROM items WHERE (id > 1) LIMIT 1"
376
+ MODEL_DB.sqls.should == ["SELECT * FROM items WHERE (id > 1) LIMIT 1"]
365
377
 
366
378
  @c.find {(:x.sql_number > 1) & (:y.sql_number < 2)}.should be_a_kind_of(@c)
367
- $sqls.last.should == "SELECT * FROM items WHERE ((x > 1) AND (y < 2)) LIMIT 1"
379
+ MODEL_DB.sqls.should == ["SELECT * FROM items WHERE ((x > 1) AND (y < 2)) LIMIT 1"]
368
380
  end
369
-
370
381
  end
371
382
 
372
383
  describe Sequel::Model, ".fetch" do
373
-
374
- before(:each) do
384
+ before do
375
385
  MODEL_DB.reset
376
386
  @c = Class.new(Sequel::Model(:items))
377
387
  end
@@ -388,80 +398,53 @@ describe Sequel::Model, ".fetch" do
388
398
  end
389
399
 
390
400
  describe Sequel::Model, ".find_or_create" do
391
-
392
- before(:each) do
393
- MODEL_DB.reset
401
+ before do
394
402
  @c = Class.new(Sequel::Model(:items)) do
395
- no_primary_key
403
+ set_primary_key :id
396
404
  columns :x
397
405
  end
406
+ MODEL_DB.reset
398
407
  end
399
408
 
400
409
  it "should find the record" do
401
- @c.find_or_create(:x => 1)
410
+ @c.find_or_create(:x => 1).should == @c.load(:x=>1, :id=>1)
402
411
  MODEL_DB.sqls.should == ["SELECT * FROM items WHERE (x = 1) LIMIT 1"]
403
-
404
- MODEL_DB.reset
405
412
  end
406
413
 
407
414
  it "should create the record if not found" do
408
- @c.meta_def(:find) do |*args|
409
- dataset.filter(*args).first
410
- nil
411
- end
412
-
413
- @c.find_or_create(:x => 1)
414
- MODEL_DB.sqls.should == [
415
- "SELECT * FROM items WHERE (x = 1) LIMIT 1",
416
- "INSERT INTO items (x) VALUES (1)"
417
- ]
415
+ @c.dataset._fetch = [[], {:x=>1, :id=>1}]
416
+ @c.dataset.autoid = 1
417
+ @c.find_or_create(:x => 1).should == @c.load(:x=>1, :id=>1)
418
+ MODEL_DB.sqls.should == ["SELECT * FROM items WHERE (x = 1) LIMIT 1",
419
+ "INSERT INTO items (x) VALUES (1)",
420
+ "SELECT * FROM items WHERE (id = 1) LIMIT 1"]
418
421
  end
419
422
 
420
423
  it "should pass the new record to be created to the block if no record is found" do
421
- @c.meta_def(:find){|*|}
422
- @c.find_or_create(:x => 1){|x| x[:y] = 2}
423
- ["INSERT INTO items (x, y) VALUES (1, 2)", "INSERT INTO items (y, x) VALUES (2, 1)"].should include(MODEL_DB.sqls.first)
424
+ @c.dataset._fetch = [[], {:x=>1, :id=>1}]
425
+ @c.dataset.autoid = 1
426
+ @c.find_or_create(:x => 1){|x| x[:y] = 2}.should == @c.load(:x=>1, :id=>1)
427
+ sqls = MODEL_DB.sqls
428
+ sqls.first.should == "SELECT * FROM items WHERE (x = 1) LIMIT 1"
429
+ ["INSERT INTO items (x, y) VALUES (1, 2)", "INSERT INTO items (y, x) VALUES (2, 1)"].should include(sqls[1])
430
+ sqls.last.should == "SELECT * FROM items WHERE (id = 1) LIMIT 1"
424
431
  end
425
432
  end
426
433
 
427
434
  describe Sequel::Model, ".all" do
428
-
429
- before(:each) do
430
- MODEL_DB.reset
431
- @c = Class.new(Sequel::Model(:items)) do
432
- no_primary_key
433
- end
434
-
435
- @c.dataset.meta_def(:all) {1234}
436
- end
437
-
438
435
  it "should return all records in the dataset" do
439
- @c.all.should == 1234
440
- end
441
-
442
- end
443
-
444
- class DummyModelBased < Sequel::Model(:blog)
445
- end
446
-
447
- describe Sequel::Model, "(:tablename)" do
448
-
449
- it "should allow reopening of descendant classes" do
450
- proc do
451
- eval "class DummyModelBased < Sequel::Model(:blog); end"
452
- end.should_not raise_error
436
+ c = Class.new(Sequel::Model(:items))
437
+ c.all.should == [c.load(:x=>1, :id=>1)]
453
438
  end
454
-
455
439
  end
456
440
 
457
441
  describe Sequel::Model, "A model class without a primary key" do
458
-
459
- before(:each) do
460
- MODEL_DB.reset
442
+ before do
461
443
  @c = Class.new(Sequel::Model(:items)) do
462
444
  columns :x
463
445
  no_primary_key
464
446
  end
447
+ MODEL_DB.reset
465
448
  end
466
449
 
467
450
  it "should be able to insert records without selecting them back" do
@@ -474,8 +457,11 @@ describe Sequel::Model, "A model class without a primary key" do
474
457
  end
475
458
 
476
459
  it "should raise when deleting" do
477
- o = @c.new
478
- proc {o.delete}.should raise_error
460
+ proc{@c.load(:x=>1).delete}.should raise_error
461
+ end
462
+
463
+ it "should raise when updating" do
464
+ proc{@c.load(:x=>1).update(:x=>2)}.should raise_error
479
465
  end
480
466
 
481
467
  it "should insert a record when saving" do
@@ -484,24 +470,17 @@ describe Sequel::Model, "A model class without a primary key" do
484
470
  o.save
485
471
  MODEL_DB.sqls.should == ['INSERT INTO items (x) VALUES (2)']
486
472
  end
487
-
488
473
  end
489
474
 
490
475
  describe Sequel::Model, "attribute accessors" do
491
476
  before do
492
- MODEL_DB.reset
493
- @dataset = Sequel::Dataset.new(MODEL_DB)
494
- def @dataset.columns; [:x, :y]; end
495
- @c = Class.new(Sequel::Model) do
496
- def self.db_schema
497
- set_columns(Array(@columns))
498
- @db_schema = {:x=>{:type=>:integer}, :y=>{:type=>:integer}}
499
- end
500
- def self.set_dataset(ds, opts={})
501
- @columns = ds.columns
502
- db_schema
503
- end
477
+ db = Sequel.mock
478
+ def db.schema(*)
479
+ [[:x, {:type=>:integer}], [:y, {:type=>:integer}]]
504
480
  end
481
+ @dataset = db[:items].columns(:x, :y)
482
+ @c = Class.new(Sequel::Model)
483
+ MODEL_DB.reset
505
484
  end
506
485
 
507
486
  it "should be created on set_dataset" do
@@ -533,7 +512,7 @@ describe Sequel::Model, "attribute accessors" do
533
512
  end
534
513
 
535
514
  it "should have a working typecasting setter even if the column is not selected" do
536
- @c.set_dataset(@dataset.select(:y))
515
+ @c.set_dataset(@dataset.select(:y).columns(:y))
537
516
  o = @c.new
538
517
 
539
518
  o.x = '34'
@@ -541,7 +520,7 @@ describe Sequel::Model, "attribute accessors" do
541
520
  end
542
521
 
543
522
  it "should typecast if the new value is the same as the existing but has a different class" do
544
- @c.set_dataset(@dataset.select(:y))
523
+ @c.set_dataset(@dataset.select(:y).columns(:y))
545
524
  o = @c.new
546
525
 
547
526
  o.x = 34
@@ -553,58 +532,51 @@ describe Sequel::Model, "attribute accessors" do
553
532
  end
554
533
 
555
534
  describe Sequel::Model, ".[]" do
556
-
557
- before(:each) do
558
- MODEL_DB.reset
559
-
535
+ before do
560
536
  @c = Class.new(Sequel::Model(:items))
561
-
562
- $cache_dataset_row = {:name => 'sharon', :id => 1}
563
- @dataset = @c.dataset
564
- $sqls = []
565
- @dataset.extend(Module.new {
566
- def fetch_rows(sql)
567
- $sqls << sql
568
- yield $cache_dataset_row
569
- end
570
- })
537
+ @c.dataset._fetch = {:name => 'sharon', :id => 1}
538
+ MODEL_DB.reset
571
539
  end
572
540
 
573
541
  it "should return the first record for the given pk" do
574
- @c[1].should be_a_kind_of(@c)
575
- $sqls.last.should == "SELECT * FROM items WHERE (id = 1) LIMIT 1"
576
- @c[9999].should be_a_kind_of(@c)
577
- $sqls.last.should == "SELECT * FROM items WHERE (id = 9999) LIMIT 1"
542
+ @c[1].should == @c.load(:name => 'sharon', :id => 1)
543
+ MODEL_DB.sqls.should == ["SELECT * FROM items WHERE id = 1"]
544
+ @c[9999].should == @c.load(:name => 'sharon', :id => 1)
545
+ MODEL_DB.sqls.should == ["SELECT * FROM items WHERE id = 9999"]
578
546
  end
579
547
 
580
548
  it "should work correctly for custom primary key" do
581
549
  @c.set_primary_key :name
582
- @c['sharon'].should be_a_kind_of(@c)
583
- $sqls.last.should == "SELECT * FROM items WHERE (name = 'sharon') LIMIT 1"
550
+ @c['sharon'].should == @c.load(:name => 'sharon', :id => 1)
551
+ MODEL_DB.sqls.should == ["SELECT * FROM items WHERE name = 'sharon'"]
552
+ end
553
+
554
+ it "should return the first record for the given pk for a filtered dataset" do
555
+ @c.dataset = @c.dataset.filter(:active=>true)
556
+ @c[1].should == @c.load(:name => 'sharon', :id => 1)
557
+ MODEL_DB.sqls.should == ["SELECT * FROM items WHERE ((active IS TRUE) AND (id = 1)) LIMIT 1"]
584
558
  end
585
559
 
586
560
  it "should work correctly for composite primary key specified as array" do
587
561
  @c.set_primary_key [:node_id, :kind]
588
562
  @c[3921, 201].should be_a_kind_of(@c)
589
- $sqls.last.should =~ \
590
- /^SELECT \* FROM items WHERE \((\(node_id = 3921\) AND \(kind = 201\))|(\(kind = 201\) AND \(node_id = 3921\))\) LIMIT 1$/
563
+ sqls = MODEL_DB.sqls
564
+ sqls.length.should == 1
565
+ sqls.first.should =~ /^SELECT \* FROM items WHERE \((\(node_id = 3921\) AND \(kind = 201\))|(\(kind = 201\) AND \(node_id = 3921\))\) LIMIT 1$/
591
566
  end
592
567
 
593
568
  it "should work correctly for composite primary key specified as separate arguments" do
594
569
  @c.set_primary_key :node_id, :kind
595
570
  @c[3921, 201].should be_a_kind_of(@c)
596
- $sqls.last.should =~ \
597
- /^SELECT \* FROM items WHERE \((\(node_id = 3921\) AND \(kind = 201\))|(\(kind = 201\) AND \(node_id = 3921\))\) LIMIT 1$/
571
+ sqls = MODEL_DB.sqls
572
+ sqls.length.should == 1
573
+ sqls.first.should =~ /^SELECT \* FROM items WHERE \((\(node_id = 3921\) AND \(kind = 201\))|(\(kind = 201\) AND \(node_id = 3921\))\) LIMIT 1$/
598
574
  end
599
575
  end
600
576
 
601
577
  describe "Model#inspect" do
602
- before do
603
- @o = Sequel::Model.load(:x => 333)
604
- end
605
-
606
578
  specify "should include the class name and the values" do
607
- @o.inspect.should == '#<Sequel::Model @values={:x=>333}>'
579
+ Sequel::Model.load(:x => 333).inspect.should == '#<Sequel::Model @values={:x=>333}>'
608
580
  end
609
581
  end
610
582
 
@@ -613,16 +585,13 @@ describe "Model.db_schema" do
613
585
  @c = Class.new(Sequel::Model(:items)) do
614
586
  def self.columns; orig_columns; end
615
587
  end
616
- @dataset = Sequel::Dataset.new(nil).from(:items)
617
- @dataset.meta_def(:db){@db ||= Sequel::Database.new}
618
- def @dataset.naked; self; end
619
- def @dataset.columns; []; end
620
- def @dataset.def_mutation_method(*names); end
588
+ @db = Sequel.mock
589
+ @dataset = @db[:items]
621
590
  end
622
591
 
623
592
  specify "should use the database's schema_for_table and set the columns and dataset columns" do
624
593
  d = @dataset.db
625
- def d.schema(table, opts = {})
594
+ def @db.schema(table, opts = {})
626
595
  [[:x, {:type=>:integer}], [:y, {:type=>:string}]]
627
596
  end
628
597
  @c.dataset = @dataset
@@ -632,36 +601,20 @@ describe "Model.db_schema" do
632
601
  end
633
602
 
634
603
  specify "should not restrict the schema for datasets with a :select option" do
635
- ds = @dataset.select(:x, :y___z)
636
- d = ds.db
637
- def d.schema(table, opts = {})
604
+ def @c.columns; [:x, :z]; end
605
+ def @db.schema(table, opts = {})
638
606
  [[:x, {:type=>:integer}], [:y, {:type=>:string}]]
639
607
  end
640
- def @c.columns; [:x, :z]; end
641
- @c.dataset = ds
608
+ @c.dataset = @dataset.select(:x, :y___z)
642
609
  @c.db_schema.should == {:x=>{:type=>:integer}, :z=>{}, :y=>{:type=>:string}}
643
610
  end
644
611
 
645
- specify "should not use schema if the dataset uses multiple tables or custom sql" do
646
- ds = @dataset.join(:x, :id)
647
- d = ds.db
648
- e = false
649
- d.meta_def(:schema){|table, *opts| e = true}
650
- def @c.columns; [:x]; end
651
- @c.dataset = ds
652
- @c.db_schema.should == {:x=>{}}
653
- e.should == false
654
- end
655
-
656
612
  specify "should fallback to fetching records if schema raises an error" do
657
- ds = @dataset.join(:x, :id)
658
- d = ds.db
659
- def d.schema(table, opts={})
660
- raise StandardError
613
+ def @db.schema(table, opts={})
614
+ raise Sequel::Error
661
615
  end
662
- def @c.columns; [:x]; end
663
- @c.dataset = ds
664
- @c.db_schema.should == {:x=>{}}
616
+ @c.dataset = @dataset.join(:x, :id).columns(:id, :x)
617
+ @c.db_schema.should == {:x=>{}, :id=>{}}
665
618
  end
666
619
 
667
620
  specify "should automatically set a singular primary key based on the schema" do