sequel 3.28.0 → 3.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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