sequel 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,1449 @@
1
+ require File.join(File.dirname(__FILE__), '../lib/sequel')
2
+
3
+ context "Dataset" do
4
+ setup do
5
+ @dataset = Sequel::Dataset.new("db")
6
+ end
7
+
8
+ specify "should accept database and opts in initialize" do
9
+ db = 'db'
10
+ opts = {:from => :test}
11
+ d = Sequel::Dataset.new(db, opts)
12
+ d.db.should be(db)
13
+ d.opts.should be(opts)
14
+
15
+ d = Sequel::Dataset.new(db)
16
+ d.db.should be(db)
17
+ d.opts.should be_a_kind_of(Hash)
18
+ d.opts.should == {}
19
+ end
20
+
21
+ specify "should provide clone_merge for chainability." do
22
+ d1 = @dataset.clone_merge(:from => :test)
23
+ d1.class.should == @dataset.class
24
+ d1.should_not == @dataset
25
+ d1.db.should be(@dataset.db)
26
+ d1.opts[:from].should == :test
27
+ @dataset.opts[:from].should be_nil
28
+
29
+ d2 = d1.clone_merge(:order => :name)
30
+ d2.class.should == @dataset.class
31
+ d2.should_not == d1
32
+ d2.should_not == @dataset
33
+ d2.db.should be(@dataset.db)
34
+ d2.opts[:from].should == :test
35
+ d2.opts[:order].should == :name
36
+ d1.opts[:order].should be_nil
37
+ end
38
+
39
+ specify "should include Enumerable" do
40
+ Sequel::Dataset.included_modules.should include(Enumerable)
41
+ end
42
+
43
+ specify "should raise NotImplementedError for the dataset interface methods" do
44
+ proc {@dataset.fetch_rows('abc')}.should raise_error(NotImplementedError)
45
+ proc {@dataset.insert(1, 2, 3)}.should raise_error(NotImplementedError)
46
+ proc {@dataset.update(:name => 'abc')}.should raise_error(NotImplementedError)
47
+ proc {@dataset.delete}.should raise_error(NotImplementedError)
48
+ end
49
+ end
50
+
51
+ context "Dataset#clone_merge" do
52
+ setup do
53
+ @dataset = Sequel::Dataset.new(nil).from(:items)
54
+ end
55
+
56
+ specify "should return a clone self" do
57
+ clone = @dataset.clone_merge({})
58
+ clone.class.should == @dataset.class
59
+ clone.db.should == @dataset.db
60
+ clone.opts.should == @dataset.opts
61
+ end
62
+
63
+ specify "should merge the specified options" do
64
+ clone = @dataset.clone_merge(1 => 2)
65
+ clone.opts.should == {1 => 2, :from => [:items]}
66
+ end
67
+
68
+ specify "should overwrite existing options" do
69
+ clone = @dataset.clone_merge(:from => [:other])
70
+ clone.opts.should == {:from => [:other]}
71
+ end
72
+
73
+ specify "should create a clone with a deep copy of options" do
74
+ clone = @dataset.clone_merge(:from => [:other])
75
+ @dataset.opts[:from].should == [:items]
76
+ clone.opts[:from].should == [:other]
77
+ end
78
+
79
+ specify "should return an object with the same modules included" do
80
+ m = Module.new do
81
+ def __xyz__; "xyz"; end
82
+ end
83
+ @dataset.extend(m)
84
+ @dataset.clone_merge({}).should respond_to(:__xyz__)
85
+ end
86
+ end
87
+
88
+ context "A simple dataset" do
89
+ setup do
90
+ @dataset = Sequel::Dataset.new(nil).from(:test)
91
+ end
92
+
93
+ specify "should format a select statement" do
94
+ @dataset.select_sql.should == 'SELECT * FROM test'
95
+ end
96
+
97
+ specify "should format a delete statement" do
98
+ @dataset.delete_sql.should == 'DELETE FROM test'
99
+ end
100
+
101
+ specify "should format an insert statement" do
102
+ @dataset.insert_sql.should == 'INSERT INTO test DEFAULT VALUES'
103
+ @dataset.insert_sql(:name => 'wxyz', :price => 342).
104
+ should match(/INSERT INTO test \(name, price\) VALUES \('wxyz', 342\)|INSERT INTO test \(price, name\) VALUES \(342, 'wxyz'\)/)
105
+ @dataset.insert_sql('a', 2, 6.5).should ==
106
+ "INSERT INTO test VALUES ('a', 2, 6.5)"
107
+ end
108
+
109
+ specify "should format an update statement" do
110
+ @dataset.update_sql(:name => 'abc').should ==
111
+ "UPDATE test SET name = 'abc'"
112
+ end
113
+ end
114
+
115
+ context "A dataset with multiple tables in its FROM clause" do
116
+ setup do
117
+ @dataset = Sequel::Dataset.new(nil).from(:t1, :t2)
118
+ end
119
+
120
+ specify "should raise on #update_sql" do
121
+ proc {@dataset.update_sql(:a=>1)}.should raise_error
122
+ end
123
+
124
+ specify "should raise on #delete_sql" do
125
+ proc {@dataset.delete_sql}.should raise_error
126
+ end
127
+
128
+ specify "should generate a select query FROM all specified tables" do
129
+ @dataset.select_sql.should == "SELECT * FROM t1, t2"
130
+ end
131
+ end
132
+
133
+ context "Dataset#where" do
134
+ setup do
135
+ @dataset = Sequel::Dataset.new(nil).from(:test)
136
+ @d1 = @dataset.where(:region => 'Asia')
137
+ @d2 = @dataset.where('(region = ?)', 'Asia')
138
+ @d3 = @dataset.where("(a = 1)")
139
+ end
140
+
141
+ specify "should work with hashes" do
142
+ @dataset.where(:name => 'xyz', :price => 342).select_sql.
143
+ should match(/WHERE \(name = 'xyz'\) AND \(price = 342\)|WHERE \(price = 342\) AND \(name = 'xyz'\)/)
144
+ end
145
+
146
+ specify "should work with arrays (ala ActiveRecord)" do
147
+ @dataset.where('price < ? AND id in (?)', 100, [1, 2, 3]).select_sql.should ==
148
+ "SELECT * FROM test WHERE price < 100 AND id in (1, 2, 3)"
149
+ end
150
+
151
+ specify "should work with strings (custom SQL expressions)" do
152
+ @dataset.where('(a = 1 AND b = 2)').select_sql.should ==
153
+ "SELECT * FROM test WHERE (a = 1 AND b = 2)"
154
+ end
155
+
156
+ specify "should affect select, delete and update statements" do
157
+ @d1.select_sql.should == "SELECT * FROM test WHERE (region = 'Asia')"
158
+ @d1.delete_sql.should == "DELETE FROM test WHERE (region = 'Asia')"
159
+ @d1.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (region = 'Asia')"
160
+
161
+ @d2.select_sql.should == "SELECT * FROM test WHERE (region = 'Asia')"
162
+ @d2.delete_sql.should == "DELETE FROM test WHERE (region = 'Asia')"
163
+ @d2.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (region = 'Asia')"
164
+
165
+ @d3.select_sql.should == "SELECT * FROM test WHERE (a = 1)"
166
+ @d3.delete_sql.should == "DELETE FROM test WHERE (a = 1)"
167
+ @d3.update_sql(:GDP => 0).should == "UPDATE test SET GDP = 0 WHERE (a = 1)"
168
+
169
+ end
170
+
171
+ specify "should be composable using AND operator (for scoping)" do
172
+ # hashes are merged, no problem
173
+ @d1.where(:size => 'big').select_sql.should ==
174
+ "SELECT * FROM test WHERE (region = 'Asia') AND (size = 'big')"
175
+
176
+ # hash and string
177
+ @d1.where('population > 1000').select_sql.should ==
178
+ "SELECT * FROM test WHERE (region = 'Asia') AND (population > 1000)"
179
+ @d1.where('(a > 1) OR (b < 2)').select_sql.should ==
180
+ "SELECT * FROM test WHERE (region = 'Asia') AND ((a > 1) OR (b < 2))"
181
+
182
+ # hash and array
183
+ @d1.where('(GDP > ?)', 1000).select_sql.should ==
184
+ "SELECT * FROM test WHERE (region = 'Asia') AND (GDP > 1000)"
185
+
186
+ # array and array
187
+ @d2.where('(GDP > ?)', 1000).select_sql.should ==
188
+ "SELECT * FROM test WHERE (region = 'Asia') AND (GDP > 1000)"
189
+
190
+ # array and hash
191
+ @d2.where(:name => ['Japan', 'China']).select_sql.should ==
192
+ "SELECT * FROM test WHERE (region = 'Asia') AND (name IN ('Japan', 'China'))"
193
+
194
+ # array and string
195
+ @d2.where('GDP > ?').select_sql.should ==
196
+ "SELECT * FROM test WHERE (region = 'Asia') AND (GDP > ?)"
197
+
198
+ # string and string
199
+ @d3.where('b = 2').select_sql.should ==
200
+ "SELECT * FROM test WHERE (a = 1) AND (b = 2)"
201
+
202
+ # string and hash
203
+ @d3.where(:c => 3).select_sql.should ==
204
+ "SELECT * FROM test WHERE (a = 1) AND (c = 3)"
205
+
206
+ # string and array
207
+ @d3.where('(d = ?)', 4).select_sql.should ==
208
+ "SELECT * FROM test WHERE (a = 1) AND (d = 4)"
209
+
210
+ # string and proc expr
211
+ @d3.where {e < 5}.select_sql.should ==
212
+ "SELECT * FROM test WHERE (a = 1) AND (e < 5)"
213
+ end
214
+
215
+ specify "should raise if the dataset is grouped" do
216
+ proc {@dataset.group(:t).where(:a => 1)}.should raise_error
217
+ end
218
+
219
+ specify "should accept ranges" do
220
+ @dataset.filter(:id => 4..7).sql.should ==
221
+ 'SELECT * FROM test WHERE (id >= 4 AND id <= 7)'
222
+ @dataset.filter(:id => 4...7).sql.should ==
223
+ 'SELECT * FROM test WHERE (id >= 4 AND id < 7)'
224
+
225
+ @dataset.filter {id == (4..7)}.sql.should ==
226
+ 'SELECT * FROM test WHERE (id >= 4 AND id <= 7)'
227
+
228
+ @dataset.filter {id.in 4..7}.sql.should ==
229
+ 'SELECT * FROM test WHERE (id >= 4 AND id <= 7)'
230
+ end
231
+
232
+ specify "should accept nil" do
233
+ @dataset.filter(:owner_id => nil).sql.should ==
234
+ 'SELECT * FROM test WHERE (owner_id IS NULL)'
235
+
236
+ @dataset.filter{owner_id.nil?}.sql.should ==
237
+ 'SELECT * FROM test WHERE (owner_id IS NULL)'
238
+ end
239
+
240
+ specify "should accept a subquery" do
241
+ # select all countries that have GDP greater than the average for Asia
242
+ @dataset.filter('gdp > ?', @d1.select(:gdp.AVG)).sql.should ==
243
+ "SELECT * FROM test WHERE gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia'))"
244
+
245
+ @dataset.filter(:id => @d1.select(:id)).sql.should ==
246
+ "SELECT * FROM test WHERE (id IN (SELECT id FROM test WHERE (region = 'Asia')))"
247
+ end
248
+
249
+ specify "should accept a subquery for an EXISTS clause" do
250
+ a = @dataset.filter {price < 100}
251
+ @dataset.filter(a.exists).sql.should ==
252
+ 'SELECT * FROM test WHERE EXISTS (SELECT 1 FROM test WHERE (price < 100))'
253
+ end
254
+
255
+ specify "should accept proc expressions (nice!)" do
256
+ d = @d1.select(:gdp.AVG)
257
+ @dataset.filter {gdp > d}.sql.should ==
258
+ "SELECT * FROM test WHERE (gdp > (SELECT avg(gdp) FROM test WHERE (region = 'Asia')))"
259
+
260
+ @dataset.filter {id.in 4..7}.sql.should ==
261
+ 'SELECT * FROM test WHERE (id >= 4 AND id <= 7)'
262
+
263
+ @dataset.filter {c == 3}.sql.should ==
264
+ 'SELECT * FROM test WHERE (c = 3)'
265
+
266
+ @dataset.filter {id == :items__id}.sql.should ==
267
+ 'SELECT * FROM test WHERE (id = items.id)'
268
+
269
+ @dataset.filter {a < 1}.sql.should ==
270
+ 'SELECT * FROM test WHERE (a < 1)'
271
+
272
+ @dataset.filter {a <=> 1}.sql.should ==
273
+ 'SELECT * FROM test WHERE NOT (a = 1)'
274
+
275
+ @dataset.filter {a >= 1 && b <= 2}.sql.should ==
276
+ 'SELECT * FROM test WHERE (a >= 1) AND (b <= 2)'
277
+
278
+ @dataset.filter {c =~ 'ABC%'}.sql.should ==
279
+ "SELECT * FROM test WHERE (c LIKE 'ABC%')"
280
+
281
+ @dataset.filter {test.ccc =~ 'ABC%'}.sql.should ==
282
+ "SELECT * FROM test WHERE (test.ccc LIKE 'ABC%')"
283
+ end
284
+
285
+ specify "should raise SequelError for invalid proc expressions" do
286
+ proc {@dataset.filter {Object.czxczxcz}}.should raise_error(SequelError)
287
+ proc {@dataset.filter {a.bcvxv}}.should raise_error(SequelError)
288
+ proc {@dataset.filter {x}}.should raise_error(SequelError)
289
+ end
290
+ end
291
+
292
+ context "Dataset#or" do
293
+ setup do
294
+ @dataset = Sequel::Dataset.new(nil).from(:test)
295
+ @d1 = @dataset.where(:x => 1)
296
+ end
297
+
298
+ specify "should raise if no filter exists" do
299
+ proc {@dataset.or(:a => 1)}.should raise_error(SequelError)
300
+ end
301
+
302
+ specify "should add an alternative expression to the where clause" do
303
+ @d1.or(:y => 2).sql.should ==
304
+ 'SELECT * FROM test WHERE (x = 1) OR (y = 2)'
305
+ end
306
+
307
+ specify "should accept all forms of filters" do
308
+ # probably not exhaustive, but good enough
309
+ @d1.or('(y > ?)', 2).sql.should ==
310
+ 'SELECT * FROM test WHERE (x = 1) OR (y > 2)'
311
+
312
+ (@d1.or {yy > 3}).sql.should ==
313
+ 'SELECT * FROM test WHERE (x = 1) OR (yy > 3)'
314
+ end
315
+
316
+ specify "should correctly add parens to give predictable results" do
317
+ @d1.filter(:y => 2).or(:z => 3).sql.should ==
318
+ 'SELECT * FROM test WHERE ((x = 1) AND (y = 2)) OR (z = 3)'
319
+
320
+ @d1.or(:y => 2).filter(:z => 3).sql.should ==
321
+ 'SELECT * FROM test WHERE ((x = 1) OR (y = 2)) AND (z = 3)'
322
+ end
323
+ end
324
+
325
+ context "Dataset#and" do
326
+ setup do
327
+ @dataset = Sequel::Dataset.new(nil).from(:test)
328
+ @d1 = @dataset.where(:x => 1)
329
+ end
330
+
331
+ specify "should raise if no filter exists" do
332
+ proc {@dataset.and(:a => 1)}.should raise_error(SequelError)
333
+ end
334
+
335
+ specify "should add an alternative expression to the where clause" do
336
+ @d1.and(:y => 2).sql.should ==
337
+ 'SELECT * FROM test WHERE (x = 1) AND (y = 2)'
338
+ end
339
+
340
+ specify "should accept all forms of filters" do
341
+ # probably not exhaustive, but good enough
342
+ @d1.and('(y > ?)', 2).sql.should ==
343
+ 'SELECT * FROM test WHERE (x = 1) AND (y > 2)'
344
+
345
+ (@d1.and {yy > 3}).sql.should ==
346
+ 'SELECT * FROM test WHERE (x = 1) AND (yy > 3)'
347
+ end
348
+
349
+ specify "should correctly add parens to give predictable results" do
350
+ @d1.or(:y => 2).and(:z => 3).sql.should ==
351
+ 'SELECT * FROM test WHERE ((x = 1) OR (y = 2)) AND (z = 3)'
352
+
353
+ @d1.and(:y => 2).or(:z => 3).sql.should ==
354
+ 'SELECT * FROM test WHERE ((x = 1) AND (y = 2)) OR (z = 3)'
355
+ end
356
+ end
357
+
358
+ context "Dataset#exclude" do
359
+ setup do
360
+ @dataset = Sequel::Dataset.new(nil).from(:test)
361
+ end
362
+
363
+ specify "should correctly include the NOT operator when one condition is given" do
364
+ @dataset.exclude(:region=>'Asia').select_sql.should ==
365
+ "SELECT * FROM test WHERE NOT (region = 'Asia')"
366
+ end
367
+
368
+ specify "should take multiple conditions as a hash and express the logic correctly in SQL" do
369
+ @dataset.exclude(:region => 'Asia', :name => 'Japan').select_sql.
370
+ should match(Regexp.union(/WHERE NOT \(\(region = 'Asia'\) AND \(name = 'Japan'\)\)/,
371
+ /WHERE NOT \(\(name = 'Japan'\) AND \(region = 'Asia'\)\)/))
372
+ end
373
+
374
+ specify "should parenthesize a single string condition correctly" do
375
+ @dataset.exclude("region = 'Asia' AND name = 'Japan'").select_sql.should ==
376
+ "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
377
+ end
378
+
379
+ specify "should parenthesize an array condition correctly" do
380
+ @dataset.exclude('region = ? AND name = ?', 'Asia', 'Japan').select_sql.should ==
381
+ "SELECT * FROM test WHERE NOT (region = 'Asia' AND name = 'Japan')"
382
+ end
383
+
384
+ specify "should corrently parenthesize when it is used twice" do
385
+ @dataset.exclude(:region => 'Asia').exclude(:name => 'Japan').select_sql.should ==
386
+ "SELECT * FROM test WHERE NOT (region = 'Asia') AND NOT (name = 'Japan')"
387
+ end
388
+
389
+ specify "should support proc expressions" do
390
+ @dataset.exclude {id == (6...12)}.sql.should ==
391
+ 'SELECT * FROM test WHERE NOT ((id >= 6 AND id < 12))'
392
+ end
393
+ end
394
+
395
+ context "Dataset#having" do
396
+ setup do
397
+ @dataset = Sequel::Dataset.new(nil).from(:test)
398
+ @grouped = @dataset.group(:region).select(:region, :population.SUM, :gdp.AVG)
399
+ @d1 = @grouped.having('sum(population) > 10')
400
+ @d2 = @grouped.having(:region => 'Asia')
401
+ @fields = "region, sum(population), avg(gdp)"
402
+ end
403
+
404
+ specify "should raise if the dataset is not grouped" do
405
+ proc {@dataset.having('avg(gdp) > 10')}.should raise_error
406
+ end
407
+
408
+ specify "should affect select statements" do
409
+ @d1.select_sql.should ==
410
+ "SELECT #{@fields} FROM test GROUP BY region HAVING sum(population) > 10"
411
+ end
412
+
413
+ specify "should support proc expressions" do
414
+ @grouped.having {SUM(:population) > 10}.sql.should ==
415
+ "SELECT #{@fields} FROM test GROUP BY region HAVING (sum(population) > 10)"
416
+ end
417
+ end
418
+
419
+ context "a grouped dataset" do
420
+ setup do
421
+ @dataset = Sequel::Dataset.new(nil).from(:test).group(:type_id)
422
+ end
423
+
424
+ specify "should raise when trying to generate an update statement" do
425
+ proc {@dataset.update_sql(:id => 0)}.should raise_error
426
+ end
427
+
428
+ specify "should raise when trying to generate a delete statement" do
429
+ proc {@dataset.delete_sql}.should raise_error
430
+ end
431
+
432
+ specify "should specify the grouping in generated select statement" do
433
+ @dataset.select_sql.should ==
434
+ "SELECT * FROM test GROUP BY type_id"
435
+ end
436
+ end
437
+
438
+
439
+ context "Dataset#literal" do
440
+ setup do
441
+ @dataset = Sequel::Dataset.new(nil).from(:test)
442
+ end
443
+
444
+ specify "should escape strings properly" do
445
+ @dataset.literal('abc').should == "'abc'"
446
+ @dataset.literal('a"x"bc').should == "'a\"x\"bc'"
447
+ @dataset.literal("a'bc").should == "'a''bc'"
448
+ @dataset.literal("a''bc").should == "'a''''bc'"
449
+ end
450
+
451
+ specify "should literalize numbers properly" do
452
+ @dataset.literal(1).should == "1"
453
+ @dataset.literal(1.5).should == "1.5"
454
+ end
455
+
456
+ specify "should literalize nil as NULL" do
457
+ @dataset.literal(nil).should == "NULL"
458
+ end
459
+
460
+ specify "should literalize an array properly" do
461
+ @dataset.literal([]).should == "NULL"
462
+ @dataset.literal([1, 'abc', 3]).should == "1, 'abc', 3"
463
+ @dataset.literal([1, "a'b''c", 3]).should == "1, 'a''b''''c', 3"
464
+ end
465
+
466
+ specify "should literalize symbols as column references" do
467
+ @dataset.literal(:name).should == "name"
468
+ @dataset.literal(:items__name).should == "items.name"
469
+ end
470
+
471
+ specify "should raise an error for unsupported types" do
472
+ proc {@dataset.literal({})}.should raise_error
473
+ end
474
+
475
+ specify "should literalize datasets as subqueries" do
476
+ d = @dataset.from(:test)
477
+ d.literal(d).should == "(#{d.sql})"
478
+ end
479
+
480
+ specify "should literalize Time properly" do
481
+ t = Time.now
482
+ s = t.strftime("TIMESTAMP '%Y-%m-%d %H:%M:%S'")
483
+ @dataset.literal(t).should == s
484
+ end
485
+
486
+ specify "should literalize Date properly" do
487
+ d = Date.today
488
+ s = d.strftime("DATE '%Y-%m-%d'")
489
+ @dataset.literal(d).should == s
490
+ end
491
+
492
+ specify "should not literalize expression strings" do
493
+ @dataset.literal('col1 + 2'.expr).should == 'col1 + 2'
494
+
495
+ @dataset.update_sql(:a => 'a + 2'.expr).should ==
496
+ 'UPDATE test SET a = a + 2'
497
+ end
498
+ end
499
+
500
+ context "Dataset#from" do
501
+ setup do
502
+ @dataset = Sequel::Dataset.new(nil)
503
+ end
504
+
505
+ specify "should accept a Dataset" do
506
+ proc {@dataset.from(@dataset)}.should_not raise_error
507
+ end
508
+
509
+ specify "should format a Dataset as a subquery if it has had options set" do
510
+ @dataset.from(@dataset.from(:a).where(:a=>1)).select_sql.should ==
511
+ "SELECT * FROM (SELECT * FROM a WHERE (a = 1))"
512
+ end
513
+
514
+ specify "should use the relevant table name if given a simple dataset" do
515
+ @dataset.from(@dataset.from(:a)).select_sql.should ==
516
+ "SELECT * FROM a"
517
+ end
518
+
519
+ specify "should raise if no source is given" do
520
+ proc {@dataset.from(@dataset.from).select_sql}.should raise_error(SequelError)
521
+ end
522
+ end
523
+
524
+ context "Dataset#select" do
525
+ setup do
526
+ @d = Sequel::Dataset.new(nil).from(:test)
527
+ end
528
+
529
+ specify "should accept variable arity" do
530
+ @d.select(:name).sql.should == 'SELECT name FROM test'
531
+ @d.select(:a, :b, :test__c).sql.should == 'SELECT a, b, test.c FROM test'
532
+ end
533
+
534
+ specify "should accept mixed types (strings and symbols)" do
535
+ @d.select('aaa').sql.should == 'SELECT aaa FROM test'
536
+ @d.select(:a, 'b').sql.should == 'SELECT a, b FROM test'
537
+ @d.select(:test__cc, 'test.d AS e').sql.should ==
538
+ 'SELECT test.cc, test.d AS e FROM test'
539
+ @d.select('test.d AS e', :test__cc).sql.should ==
540
+ 'SELECT test.d AS e, test.cc FROM test'
541
+
542
+ # symbol helpers
543
+ @d.select(:test.ALL).sql.should ==
544
+ 'SELECT test.* FROM test'
545
+ @d.select(:test__name.AS(:n)).sql.should ==
546
+ 'SELECT test.name AS n FROM test'
547
+ @d.select(:test__name___n).sql.should ==
548
+ 'SELECT test.name AS n FROM test'
549
+ end
550
+
551
+ specify "should use the wildcard if no arguments are given" do
552
+ @d.select.sql.should == 'SELECT * FROM test'
553
+ end
554
+
555
+ specify "should overrun the previous select option" do
556
+ @d.select(:a, :b, :c).select.sql.should == 'SELECT * FROM test'
557
+ @d.select(:price).select(:name).sql.should == 'SELECT name FROM test'
558
+ end
559
+ end
560
+
561
+ context "Dataset#order" do
562
+ setup do
563
+ @dataset = Sequel::Dataset.new(nil).from(:test)
564
+ end
565
+
566
+ specify "should include an ORDER BY clause in the select statement" do
567
+ @dataset.order(:name).sql.should ==
568
+ 'SELECT * FROM test ORDER BY name'
569
+ end
570
+
571
+ specify "should accept multiple arguments" do
572
+ @dataset.order(:name, :price.DESC).sql.should ==
573
+ 'SELECT * FROM test ORDER BY name, price DESC'
574
+ end
575
+
576
+ specify "should overrun a previous ordering" do
577
+ @dataset.order(:name).order(:stamp).sql.should ==
578
+ 'SELECT * FROM test ORDER BY stamp'
579
+ end
580
+
581
+ specify "should accept a string" do
582
+ @dataset.order('dada ASC').sql.should ==
583
+ 'SELECT * FROM test ORDER BY dada ASC'
584
+ end
585
+ end
586
+
587
+ context "Dataset#reverse_order" do
588
+ setup do
589
+ @dataset = Sequel::Dataset.new(nil).from(:test)
590
+ end
591
+
592
+ specify "should use DESC as default order" do
593
+ @dataset.reverse_order(:name).sql.should ==
594
+ 'SELECT * FROM test ORDER BY name DESC'
595
+ end
596
+
597
+ specify "should invert the order given" do
598
+ @dataset.reverse_order(:name.DESC).sql.should ==
599
+ 'SELECT * FROM test ORDER BY name'
600
+ end
601
+
602
+ specify "should accept multiple arguments" do
603
+ @dataset.reverse_order(:name, :price.DESC).sql.should ==
604
+ 'SELECT * FROM test ORDER BY name DESC, price'
605
+ end
606
+
607
+ specify "should reverse a previous ordering if no arguments are given" do
608
+ @dataset.order(:name).reverse_order.sql.should ==
609
+ 'SELECT * FROM test ORDER BY name DESC'
610
+ @dataset.order('clumsy DESC, fool').reverse_order.sql.should ==
611
+ 'SELECT * FROM test ORDER BY clumsy, fool DESC'
612
+ end
613
+ end
614
+
615
+ context "Dataset#limit" do
616
+ setup do
617
+ @dataset = Sequel::Dataset.new(nil).from(:test)
618
+ end
619
+
620
+ specify "should include a LIMIT clause in the select statement" do
621
+ @dataset.limit(10).sql.should ==
622
+ 'SELECT * FROM test LIMIT 10'
623
+ end
624
+
625
+ specify "should accept ranges" do
626
+ @dataset.limit(3..7).sql.should ==
627
+ 'SELECT * FROM test LIMIT 5 OFFSET 3'
628
+
629
+ @dataset.limit(3...7).sql.should ==
630
+ 'SELECT * FROM test LIMIT 4 OFFSET 3'
631
+ end
632
+
633
+ specify "should include an offset if a second argument is given" do
634
+ @dataset.limit(6, 10).sql.should ==
635
+ 'SELECT * FROM test LIMIT 6 OFFSET 10'
636
+ end
637
+ end
638
+
639
+ context "Dataset#naked" do
640
+ setup do
641
+ @d1 = Sequel::Dataset.new(nil, {1 => 2, 3 => 4})
642
+ @d2 = Sequel::Dataset.new(nil, {1 => 2, 3 => 4}).set_model(Object)
643
+ end
644
+
645
+ specify "should return a clone with :naked option set" do
646
+ naked = @d1.naked
647
+ naked.opts[:naked].should be_true
648
+ end
649
+
650
+ specify "should remove any existing reference to a model class" do
651
+ naked = @d2.naked
652
+ naked.opts[:models].should be_nil
653
+ end
654
+ end
655
+
656
+ context "Dataset#qualified_field_name" do
657
+ setup do
658
+ @dataset = Sequel::Dataset.new(nil).from(:test)
659
+ end
660
+
661
+ specify "should return the same if already qualified" do
662
+ @dataset.qualified_field_name('test.a', :items).should == 'test.a'
663
+ @dataset.qualified_field_name(:ccc__b, :items).should == 'ccc.b'
664
+ end
665
+
666
+ specify "should qualify the field with the supplied table name" do
667
+ @dataset.qualified_field_name('a', :items).should == 'items.a'
668
+ @dataset.qualified_field_name(:b1, :items).should == 'items.b1'
669
+ end
670
+ end
671
+
672
+ class DummyDataset < Sequel::Dataset
673
+ VALUES = [
674
+ {:a => 1, :b => 2},
675
+ {:a => 3, :b => 4},
676
+ {:a => 5, :b => 6}
677
+ ]
678
+ def fetch_rows(sql, &block)
679
+ VALUES.each(&block)
680
+ end
681
+ end
682
+
683
+ context "Dataset#map" do
684
+ setup do
685
+ @d = DummyDataset.new(nil).from(:items)
686
+ end
687
+
688
+ specify "should provide the usual functionality if no argument is given" do
689
+ @d.map {|n| n[:a] + n[:b]}.should == [3, 7, 11]
690
+ end
691
+
692
+ specify "should map using #[fieldname] if fieldname is given" do
693
+ @d.map(:a).should == [1, 3, 5]
694
+ end
695
+
696
+ specify "should return the complete dataset values if nothing is given" do
697
+ @d.map.should == DummyDataset::VALUES
698
+ end
699
+ end
700
+
701
+ context "Dataset#to_hash" do
702
+ setup do
703
+ @d = DummyDataset.new(nil).from(:items)
704
+ end
705
+
706
+ specify "should provide a hash with the first field as key and the second as value" do
707
+ @d.to_hash(:a, :b).should == {1 => 2, 3 => 4, 5 => 6}
708
+ @d.to_hash(:b, :a).should == {2 => 1, 4 => 3, 6 => 5}
709
+ end
710
+ end
711
+
712
+ context "Dataset#uniq" do
713
+ setup do
714
+ @dataset = Sequel::Dataset.new(nil).from(:test).select(:name)
715
+ end
716
+
717
+ specify "should include DISTINCT clause in statement" do
718
+ @dataset.uniq.sql.should == 'SELECT DISTINCT name FROM test'
719
+ end
720
+
721
+ specify "should be aliased by Dataset#distinct" do
722
+ @dataset.distinct.sql.should == 'SELECT DISTINCT name FROM test'
723
+ end
724
+ end
725
+
726
+ context "Dataset#count" do
727
+ setup do
728
+ @c = Class.new(Sequel::Dataset) do
729
+ def self.sql
730
+ @@sql
731
+ end
732
+
733
+ def fetch_rows(sql)
734
+ @@sql = sql
735
+ yield({1 => 1})
736
+ end
737
+ end
738
+ @dataset = @c.new(nil).from(:test)
739
+ end
740
+
741
+ specify "should format SQL propertly" do
742
+ @dataset.count.should == 1
743
+ @c.sql.should == 'SELECT COUNT(*) FROM test'
744
+ end
745
+
746
+ specify "should be aliased by #size" do
747
+ @dataset.size.should == 1
748
+ end
749
+
750
+ specify "should include the where clause if it's there" do
751
+ @dataset.filter {abc < 30}.count.should == 1
752
+ @c.sql.should == 'SELECT COUNT(*) FROM test WHERE (abc < 30)'
753
+ end
754
+ end
755
+
756
+ context "Dataset#join_table" do
757
+ setup do
758
+ @d = Sequel::Dataset.new(nil).from(:items)
759
+ end
760
+
761
+ specify "should format the JOIN clause properly" do
762
+ @d.join_table(:left_outer, :categories, :category_id => :id).sql.should ==
763
+ 'SELECT * FROM items LEFT OUTER JOIN categories ON (categories.category_id = items.id)'
764
+ end
765
+
766
+ specify "should include WHERE clause if applicable" do
767
+ @d.filter {price < 100}.join_table(:right_outer, :categories, :category_id => :id).sql.should ==
768
+ 'SELECT * FROM items RIGHT OUTER JOIN categories ON (categories.category_id = items.id) WHERE (price < 100)'
769
+ end
770
+
771
+ specify "should include ORDER BY clause if applicable" do
772
+ @d.order(:stamp).join_table(:full_outer, :categories, :category_id => :id).sql.should ==
773
+ 'SELECT * FROM items FULL OUTER JOIN categories ON (categories.category_id = items.id) ORDER BY stamp'
774
+ end
775
+
776
+ specify "should support multiple joins" do
777
+ @d.join_table(:inner, :b, :items_id).join_table(:left_outer, :c, :b_id => :b__id).sql.should ==
778
+ 'SELECT * FROM items INNER JOIN b ON (b.items_id = items.id) LEFT OUTER JOIN c ON (c.b_id = b.id)'
779
+ end
780
+
781
+ specify "should use id as implicit relation primary key if ommited" do
782
+ @d.join_table(:left_outer, :categories, :category_id).sql.should ==
783
+ @d.join_table(:left_outer, :categories, :category_id => :id).sql
784
+
785
+ # when doing multiple joins, id should be qualified using the last joined table
786
+ @d.join_table(:right_outer, :b, :items_id).join_table(:full_outer, :c, :b_id).sql.should ==
787
+ 'SELECT * FROM items RIGHT OUTER JOIN b ON (b.items_id = items.id) FULL OUTER JOIN c ON (c.b_id = b.id)'
788
+ end
789
+
790
+ specify "should support left outer joins" do
791
+ @d.join_table(:left_outer, :categories, :category_id).sql.should ==
792
+ 'SELECT * FROM items LEFT OUTER JOIN categories ON (categories.category_id = items.id)'
793
+
794
+ @d.left_outer_join(:categories, :category_id).sql.should ==
795
+ 'SELECT * FROM items LEFT OUTER JOIN categories ON (categories.category_id = items.id)'
796
+ end
797
+
798
+ specify "should support right outer joins" do
799
+ @d.join_table(:right_outer, :categories, :category_id).sql.should ==
800
+ 'SELECT * FROM items RIGHT OUTER JOIN categories ON (categories.category_id = items.id)'
801
+
802
+ @d.right_outer_join(:categories, :category_id).sql.should ==
803
+ 'SELECT * FROM items RIGHT OUTER JOIN categories ON (categories.category_id = items.id)'
804
+ end
805
+
806
+ specify "should support full outer joins" do
807
+ @d.join_table(:full_outer, :categories, :category_id).sql.should ==
808
+ 'SELECT * FROM items FULL OUTER JOIN categories ON (categories.category_id = items.id)'
809
+
810
+ @d.full_outer_join(:categories, :category_id).sql.should ==
811
+ 'SELECT * FROM items FULL OUTER JOIN categories ON (categories.category_id = items.id)'
812
+ end
813
+
814
+ specify "should support inner joins" do
815
+ @d.join_table(:inner, :categories, :category_id).sql.should ==
816
+ 'SELECT * FROM items INNER JOIN categories ON (categories.category_id = items.id)'
817
+
818
+ @d.inner_join(:categories, :category_id).sql.should ==
819
+ 'SELECT * FROM items INNER JOIN categories ON (categories.category_id = items.id)'
820
+ end
821
+
822
+ specify "should default to an inner join" do
823
+ @d.join_table(nil, :categories, :category_id).sql.should ==
824
+ 'SELECT * FROM items INNER JOIN categories ON (categories.category_id = items.id)'
825
+
826
+ @d.join(:categories, :category_id).sql.should ==
827
+ 'SELECT * FROM items INNER JOIN categories ON (categories.category_id = items.id)'
828
+ end
829
+
830
+ specify "should raise if an invalid join type is specified" do
831
+ proc {@d.join_table(:invalid, :a, :b)}.should raise_error(SequelError)
832
+ end
833
+ end
834
+
835
+ context "Dataset#[]=" do
836
+ setup do
837
+ c = Class.new(Sequel::Dataset) do
838
+ def last_sql
839
+ @@last_sql
840
+ end
841
+
842
+ def update(*args)
843
+ @@last_sql = update_sql(*args)
844
+ end
845
+ end
846
+
847
+ @d = c.new(nil).from(:items)
848
+ end
849
+
850
+ specify "should perform an update on the specified filter" do
851
+ @d[:a => 1] = {:x => 3}
852
+ @d.last_sql.should == 'UPDATE items SET x = 3 WHERE (a = 1)'
853
+ end
854
+ end
855
+
856
+ context "Dataset#insert_multiple" do
857
+ setup do
858
+ c = Class.new(Sequel::Dataset) do
859
+ attr_reader :inserts
860
+ def insert(arg)
861
+ @inserts ||= []
862
+ @inserts << arg
863
+ end
864
+ end
865
+
866
+ @d = c.new(nil)
867
+ end
868
+
869
+ specify "should insert all items in the supplied array" do
870
+ @d.insert_multiple [:aa, 5, 3, {1 => 2}]
871
+ @d.inserts.should == [:aa, 5, 3, {1 => 2}]
872
+ end
873
+
874
+ specify "should pass array items through the supplied block if given" do
875
+ a = ["inevitable", "hello", "the ticking clock"]
876
+ @d.insert_multiple(a) {|i| i.gsub('l', 'r')}
877
+ @d.inserts.should == ["inevitabre", "herro", "the ticking crock"]
878
+ end
879
+ end
880
+
881
+ context "Dataset aggregate methods" do
882
+ setup do
883
+ c = Class.new(Sequel::Dataset) do
884
+ def fetch_rows(sql)
885
+ yield({1 => sql})
886
+ end
887
+ end
888
+ @d = c.new(nil).from(:test)
889
+ end
890
+
891
+ specify "should include min" do
892
+ @d.min(:a).should == 'SELECT min(a) FROM test'
893
+ end
894
+
895
+ specify "should include max" do
896
+ @d.max(:b).should == 'SELECT max(b) FROM test'
897
+ end
898
+
899
+ specify "should include sum" do
900
+ @d.sum(:c).should == 'SELECT sum(c) FROM test'
901
+ end
902
+
903
+ specify "should include avg" do
904
+ @d.avg(:d).should == 'SELECT avg(d) FROM test'
905
+ end
906
+
907
+ specify "should accept qualified fields" do
908
+ @d.avg(:test__bc).should == 'SELECT avg(test.bc) FROM test'
909
+ end
910
+ end
911
+
912
+ context "Dataset#first" do
913
+ setup do
914
+ @c = Class.new(Sequel::Dataset) do
915
+ @@last_dataset = nil
916
+ @@last_opts = nil
917
+
918
+ def self.last_dataset
919
+ @@last_dataset
920
+ end
921
+
922
+ def self.last_opts
923
+ @@last_opts
924
+ end
925
+
926
+ def single_record(opts = nil)
927
+ @@last_opts = @opts.merge(opts || {})
928
+ {:a => 1, :b => 2}
929
+ end
930
+
931
+ def all
932
+ @@last_dataset = self
933
+ [{:a => 1, :b => 2}] * @opts[:limit]
934
+ end
935
+ end
936
+ @d = @c.new(nil).from(:test)
937
+ end
938
+
939
+ specify "should return the first matching record if a hash is specified" do
940
+ @d.first(:z => 26).should == {:a => 1, :b => 2}
941
+ @c.last_opts[:where].should == ('(z = 26)')
942
+
943
+ @d.first('z = ?', 15)
944
+ @c.last_opts[:where].should == ('z = 15')
945
+ end
946
+
947
+ specify "should return a single record if no argument is given" do
948
+ @d.first.should == {:a => 1, :b => 2}
949
+ end
950
+
951
+ specify "should set the limit according to the given number" do
952
+ @d.first
953
+ @c.last_opts[:limit].should == 1
954
+
955
+ i = rand(10) + 10
956
+ @d.first(i)
957
+ @c.last_dataset.opts[:limit].should == i
958
+ end
959
+
960
+ specify "should return an array with the records if argument is greater than 1" do
961
+ i = rand(10) + 10
962
+ r = @d.first(i)
963
+ r.should be_a_kind_of(Array)
964
+ r.size.should == i
965
+ r.each {|row| row.should == {:a => 1, :b => 2}}
966
+ end
967
+ end
968
+
969
+ context "Dataset#last" do
970
+ setup do
971
+ @c = Class.new(Sequel::Dataset) do
972
+ @@last_dataset = nil
973
+
974
+ def self.last_dataset
975
+ @@last_dataset
976
+ end
977
+
978
+ def single_record(opts = nil)
979
+ @@last_dataset = clone_merge(opts) if opts
980
+ {:a => 1, :b => 2}
981
+ end
982
+
983
+ def all
984
+ @@last_dataset = self
985
+ [{:a => 1, :b => 2}] * @opts[:limit]
986
+ end
987
+ end
988
+ @d = @c.new(nil).from(:test)
989
+ end
990
+
991
+ specify "should raise if no order is given" do
992
+ proc {@d.last}.should raise_error(SequelError)
993
+ proc {@d.last(2)}.should raise_error(SequelError)
994
+ proc {@d.order(:a).last}.should_not raise_error
995
+ proc {@d.order(:a).last(2)}.should_not raise_error
996
+ end
997
+
998
+ specify "should invert the order" do
999
+ @d.order(:a).last
1000
+ @c.last_dataset.opts[:order].should == ['a DESC']
1001
+
1002
+ @d.order(:b.DESC).last
1003
+ @c.last_dataset.opts[:order].should == ['b']
1004
+
1005
+ @d.order(:c, :d).last
1006
+ @c.last_dataset.opts[:order].should == ['c DESC', 'd DESC']
1007
+
1008
+ @d.order(:e.DESC, :f).last
1009
+ @c.last_dataset.opts[:order].should == ['e', 'f DESC']
1010
+ end
1011
+
1012
+ specify "should return the first matching record if a hash is specified" do
1013
+ @d.order(:a).last(:z => 26).should == {:a => 1, :b => 2}
1014
+ @c.last_dataset.opts[:where].should == ('(z = 26)')
1015
+
1016
+ @d.order(:a).last('z = ?', 15)
1017
+ @c.last_dataset.opts[:where].should == ('z = 15')
1018
+ end
1019
+
1020
+ specify "should return a single record if no argument is given" do
1021
+ @d.order(:a).last.should == {:a => 1, :b => 2}
1022
+ end
1023
+
1024
+ specify "should set the limit according to the given number" do
1025
+ i = rand(10) + 10
1026
+ r = @d.order(:a).last(i)
1027
+ @c.last_dataset.opts[:limit].should == i
1028
+ end
1029
+
1030
+ specify "should return an array with the records if argument is greater than 1" do
1031
+ i = rand(10) + 10
1032
+ r = @d.order(:a).last(i)
1033
+ r.should be_a_kind_of(Array)
1034
+ r.size.should == i
1035
+ r.each {|row| row.should == {:a => 1, :b => 2}}
1036
+ end
1037
+ end
1038
+
1039
+ context "Dataset set operations" do
1040
+ setup do
1041
+ @a = Sequel::Dataset.new(nil).from(:a).filter(:z => 1)
1042
+ @b = Sequel::Dataset.new(nil).from(:b).filter(:z => 2)
1043
+ end
1044
+
1045
+ specify "should support UNION and UNION ALL" do
1046
+ @a.union(@b).sql.should == \
1047
+ "SELECT * FROM a WHERE (z = 1) UNION SELECT * FROM b WHERE (z = 2)"
1048
+ @b.union(@a, true).sql.should == \
1049
+ "SELECT * FROM b WHERE (z = 2) UNION ALL SELECT * FROM a WHERE (z = 1)"
1050
+ end
1051
+
1052
+ specify "should support INTERSECT and INTERSECT ALL" do
1053
+ @a.intersect(@b).sql.should == \
1054
+ "SELECT * FROM a WHERE (z = 1) INTERSECT SELECT * FROM b WHERE (z = 2)"
1055
+ @b.intersect(@a, true).sql.should == \
1056
+ "SELECT * FROM b WHERE (z = 2) INTERSECT ALL SELECT * FROM a WHERE (z = 1)"
1057
+ end
1058
+
1059
+ specify "should support EXCEPT and EXCEPT ALL" do
1060
+ @a.except(@b).sql.should == \
1061
+ "SELECT * FROM a WHERE (z = 1) EXCEPT SELECT * FROM b WHERE (z = 2)"
1062
+ @b.except(@a, true).sql.should == \
1063
+ "SELECT * FROM b WHERE (z = 2) EXCEPT ALL SELECT * FROM a WHERE (z = 1)"
1064
+ end
1065
+ end
1066
+
1067
+ context "Dataset#[]" do
1068
+ setup do
1069
+ @c = Class.new(Sequel::Dataset) do
1070
+ @@last_dataset = nil
1071
+
1072
+ def self.last_dataset
1073
+ @@last_dataset
1074
+ end
1075
+
1076
+ def single_record(opts = nil)
1077
+ @@last_dataset = opts ? clone_merge(opts) : self
1078
+ {1 => 2, 3 => 4}
1079
+ end
1080
+ end
1081
+ @d = @c.new(nil).from(:test)
1082
+ end
1083
+
1084
+ specify "should return a single record filtered according to the given conditions" do
1085
+ @d[:name => 'didi'].should == {1 => 2, 3 => 4}
1086
+ @c.last_dataset.opts[:where].should == "(name = 'didi')"
1087
+
1088
+ @d[:id => 5..45].should == {1 => 2, 3 => 4}
1089
+ @c.last_dataset.opts[:where].should == "(id >= 5 AND id <= 45)"
1090
+ end
1091
+ end
1092
+
1093
+ context "Dataset#single_record" do
1094
+ setup do
1095
+ @c = Class.new(Sequel::Dataset) do
1096
+ def fetch_rows(sql)
1097
+ yield sql
1098
+ end
1099
+ end
1100
+ @cc = Class.new(@c) do
1101
+ def fetch_rows(sql); end
1102
+ end
1103
+ @d = @c.new(nil).from(:test)
1104
+ @e = @cc.new(nil).from(:test)
1105
+ end
1106
+
1107
+ specify "should call each and return the first record" do
1108
+ @d.single_record.should == 'SELECT * FROM test'
1109
+ end
1110
+
1111
+ specify "should pass opts to each" do
1112
+ @d.single_record(:limit => 3).should == 'SELECT * FROM test LIMIT 3'
1113
+ end
1114
+
1115
+ specify "should return nil if no record is present" do
1116
+ @e.single_record.should be_nil
1117
+ end
1118
+ end
1119
+
1120
+ context "Dataset#single_value" do
1121
+ setup do
1122
+ @c = Class.new(Sequel::Dataset) do
1123
+ def fetch_rows(sql)
1124
+ yield({1 => sql})
1125
+ end
1126
+ end
1127
+ @d = @c.new(nil).from(:test)
1128
+ end
1129
+
1130
+ specify "should call each and return the first value of the first record" do
1131
+ @d.single_value.should == 'SELECT * FROM test'
1132
+ end
1133
+
1134
+ specify "should pass opts to each" do
1135
+ @d.single_value(:limit => 3).should == 'SELECT * FROM test LIMIT 3'
1136
+ end
1137
+ end
1138
+
1139
+ context "Dataset#set_model" do
1140
+ setup do
1141
+ @c = Class.new(Sequel::Dataset) do
1142
+ def fetch_rows(sql, &block)
1143
+ (1..10).each(&block)
1144
+ end
1145
+ end
1146
+ @dataset = @c.new(nil).from(:items)
1147
+ @m = Class.new do
1148
+ attr_accessor :c
1149
+ def initialize(c); @c = c; end
1150
+ def ==(o); @c == o.c; end
1151
+ end
1152
+ end
1153
+
1154
+ specify "should clear the models hash and restore the stock #each if nil is specified" do
1155
+ @dataset.set_model(@m)
1156
+ @dataset.set_model(nil)
1157
+ @dataset.first.should == 1
1158
+ @dataset.model_classes.should be_nil
1159
+ end
1160
+
1161
+ specify "should clear the models hash and restore the stock #each if nothing is specified" do
1162
+ @dataset.set_model(@m)
1163
+ @dataset.set_model
1164
+ @dataset.first.should == 1
1165
+ @dataset.model_classes.should be_nil
1166
+ end
1167
+
1168
+ specify "should alter #each to provide model instances" do
1169
+ @dataset.first.should == 1
1170
+ @dataset.set_model(@m)
1171
+ @dataset.first.should == @m.new(1)
1172
+ end
1173
+
1174
+ specify "should extend the dataset with a #destroy method" do
1175
+ @dataset.should_not respond_to(:destroy)
1176
+ @dataset.set_model(@m)
1177
+ @dataset.should respond_to(:destroy)
1178
+ end
1179
+
1180
+ specify "should set opts[:naked] to nil" do
1181
+ @dataset.opts[:naked] = true
1182
+ @dataset.set_model(@m)
1183
+ @dataset.opts[:naked].should be_nil
1184
+ end
1185
+
1186
+ specify "should provide support for polymorphic model instantiation" do
1187
+ @m1 = Class.new(@m)
1188
+ @m2 = Class.new(@m)
1189
+ @dataset.set_model(0, 0 => @m1, 1 => @m2)
1190
+ all = @dataset.all
1191
+ all[0].class.should == @m2
1192
+ all[1].class.should == @m1
1193
+ all[2].class.should == @m2
1194
+ all[3].class.should == @m1
1195
+ #...
1196
+ end
1197
+ end
1198
+
1199
+ context "Dataset#model_classes" do
1200
+ setup do
1201
+ @c = Class.new(Sequel::Dataset) do
1202
+ # # We don't need that for now
1203
+ # def fetch_rows(sql, &block)
1204
+ # (1..10).each(&block)
1205
+ # end
1206
+ end
1207
+ @dataset = @c.new(nil).from(:items)
1208
+ @m = Class.new do
1209
+ attr_accessor :c
1210
+ def initialize(c); @c = c; end
1211
+ def ==(o); @c == o.c; end
1212
+ end
1213
+ end
1214
+
1215
+ specify "should return nil for a naked dataset" do
1216
+ @dataset.model_classes.should == nil
1217
+ end
1218
+
1219
+ specify "should return a {nil => model_class} hash for a model dataset" do
1220
+ @dataset.set_model(@m)
1221
+ @dataset.model_classes.should == {nil => @m}
1222
+ end
1223
+
1224
+ specify "should return the polymorphic hash for a polymorphic model dataset" do
1225
+ @m1 = Class.new(@m)
1226
+ @m2 = Class.new(@m)
1227
+ @dataset.set_model(0, 0 => @m1, 1 => @m2)
1228
+ @dataset.model_classes.should == {0 => @m1, 1 => @m2}
1229
+ end
1230
+ end
1231
+
1232
+ context "Dataset#polymorphic_key" do
1233
+ setup do
1234
+ @c = Class.new(Sequel::Dataset) do
1235
+ # # We don't need this for now
1236
+ # def fetch_rows(sql, &block)
1237
+ # (1..10).each(&block)
1238
+ # end
1239
+ end
1240
+ @dataset = @c.new(nil).from(:items)
1241
+ @m = Class.new do
1242
+ attr_accessor :c
1243
+ def initialize(c); @c = c; end
1244
+ def ==(o); @c == o.c; end
1245
+ end
1246
+ end
1247
+
1248
+ specify "should return nil for a naked dataset" do
1249
+ @dataset.polymorphic_key.should be_nil
1250
+ end
1251
+
1252
+ specify "should return the polymorphic key" do
1253
+ @dataset.set_model(:id, nil => @m)
1254
+ @dataset.polymorphic_key.should == :id
1255
+ end
1256
+ end
1257
+
1258
+ context "A model dataset" do
1259
+ setup do
1260
+ @c = Class.new(Sequel::Dataset) do
1261
+ def fetch_rows(sql, &block)
1262
+ (1..10).each(&block)
1263
+ end
1264
+ end
1265
+ @dataset = @c.new(nil).from(:items)
1266
+ @m = Class.new do
1267
+ attr_accessor :c
1268
+ def initialize(c); @c = c; end
1269
+ def ==(o); @c == o.c; end
1270
+ end
1271
+ @dataset.set_model(@m)
1272
+ end
1273
+
1274
+ specify "should supply naked records if the naked option is specified" do
1275
+ @dataset.each {|r| r.class.should == @m}
1276
+ @dataset.each(:naked => true) {|r| r.class.should == Fixnum}
1277
+ end
1278
+ end
1279
+
1280
+ context "A polymorphic model dataset" do
1281
+ setup do
1282
+ @c = Class.new(Sequel::Dataset) do
1283
+ def fetch_rows(sql, &block)
1284
+ (1..10).each(&block)
1285
+ end
1286
+ end
1287
+ @dataset = @c.new(nil).from(:items)
1288
+ @m = Class.new do
1289
+ attr_accessor :c
1290
+ def initialize(c); @c = c; end
1291
+ def ==(o); @c == o.c; end
1292
+ end
1293
+ end
1294
+
1295
+ specify "should use a nil key in the polymorphic hash to specify the default model class" do
1296
+ @m2 = Class.new(@m)
1297
+ @dataset.set_model(0, nil => @m, 1 => @m2)
1298
+ all = @dataset.all
1299
+ all[0].class.should == @m2
1300
+ all[1].class.should == @m
1301
+ all[2].class.should == @m2
1302
+ all[3].class.should == @m
1303
+ #...
1304
+ end
1305
+
1306
+ specify "should raise SequelError if no suitable class is found in the polymorphic hash" do
1307
+ @m2 = Class.new(@m)
1308
+ @dataset.set_model(0, 1 => @m2)
1309
+ proc {@dataset.all}.should raise_error(SequelError)
1310
+ end
1311
+
1312
+ specify "should supply naked records if the naked option is specified" do
1313
+ @dataset.set_model(0, nil => @m)
1314
+ @dataset.each(:naked => true) {|r| r.class.should == Fixnum}
1315
+ end
1316
+ end
1317
+
1318
+ context "Dataset#destroy" do
1319
+ setup do
1320
+ db = Object.new
1321
+ m = Module.new do
1322
+ def transaction; yield; end
1323
+ end
1324
+ db.extend(m)
1325
+
1326
+ DESTROYED = []
1327
+
1328
+ @m = Class.new do
1329
+ def initialize(c)
1330
+ @c = c
1331
+ end
1332
+
1333
+ attr_accessor :c
1334
+
1335
+ def ==(o)
1336
+ @c == o.c
1337
+ end
1338
+
1339
+ def destroy
1340
+ DESTROYED << self
1341
+ end
1342
+ end
1343
+ MODELS = [@m.new(12), @m.new(13)]
1344
+
1345
+ c = Class.new(Sequel::Dataset) do
1346
+ def fetch_rows(sql, &block)
1347
+ (12..13).each(&block)
1348
+ end
1349
+ end
1350
+
1351
+ @d = c.new(db).from(:test)
1352
+ @d.set_model(@m)
1353
+ end
1354
+
1355
+ specify "should destroy raise for every model in the dataset" do
1356
+ count = @d.destroy
1357
+ count.should == 2
1358
+ DESTROYED.should == MODELS
1359
+ end
1360
+ end
1361
+
1362
+ context "Dataset#<<" do
1363
+ setup do
1364
+ @d = Sequel::Dataset.new(nil)
1365
+ @d.meta_def(:insert) do
1366
+ 1234567890
1367
+ end
1368
+ end
1369
+
1370
+ specify "should call #insert" do
1371
+ (@d << {:name => 1}).should == 1234567890
1372
+ end
1373
+ end
1374
+
1375
+ context "A paginated dataset" do
1376
+ setup do
1377
+ @d = Sequel::Dataset.new(nil)
1378
+ @d.meta_def(:count) {153}
1379
+
1380
+ @paginated = @d.paginate(1, 20)
1381
+ end
1382
+
1383
+ specify "should set the limit and offset options correctly" do
1384
+ @paginated.opts[:limit].should == 20
1385
+ @paginated.opts[:offset].should == 0
1386
+ end
1387
+
1388
+ specify "should set the page count correctly" do
1389
+ @paginated.page_count.should == 8
1390
+ @d.paginate(1, 50).page_count.should == 4
1391
+ end
1392
+
1393
+ specify "should set the current page number correctly" do
1394
+ @paginated.current_page.should == 1
1395
+ @d.paginate(3, 50).current_page.should == 3
1396
+ end
1397
+
1398
+ specify "should return the next page number or nil if we're on the last" do
1399
+ @paginated.next_page.should == 2
1400
+ @d.paginate(4, 50).next_page.should be_nil
1401
+ end
1402
+
1403
+ specify "should return the previous page number or nil if we're on the last" do
1404
+ @paginated.prev_page.should be_nil
1405
+ @d.paginate(4, 50).prev_page.should == 3
1406
+ end
1407
+ end
1408
+
1409
+ context "Dataset#columns" do
1410
+ setup do
1411
+ @dataset = DummyDataset.new(nil).from(:items)
1412
+ @dataset.meta_def(:columns=) {|c| @columns = c}
1413
+ @dataset.meta_def(:first) {@columns = select_sql(nil)}
1414
+ end
1415
+
1416
+ specify "should return the value of @columns" do
1417
+ @dataset.columns = [:a, :b, :c]
1418
+ @dataset.columns.should == [:a, :b, :c]
1419
+ end
1420
+
1421
+ specify "should call first if @columns is nil" do
1422
+ @dataset.columns = nil
1423
+ @dataset.columns.should == 'SELECT * FROM items'
1424
+ @dataset.opts[:from] = [:nana]
1425
+ @dataset.columns.should == 'SELECT * FROM items'
1426
+ end
1427
+ end
1428
+
1429
+ require 'stringio'
1430
+
1431
+ context "Dataset#print" do
1432
+ setup do
1433
+ @output = StringIO.new
1434
+ @orig_stdout = $stdout
1435
+ $stdout = @output
1436
+ @dataset = DummyDataset.new(nil).from(:items)
1437
+ end
1438
+
1439
+ teardown do
1440
+ $stdout = @orig_stdout
1441
+ end
1442
+
1443
+ specify "should print out a table with the values" do
1444
+ @dataset.print(:a, :b)
1445
+ @output.rewind
1446
+ @output.read.should == \
1447
+ "+-+-+\n|a|b|\n+-+-+\n|1|2|\n|3|4|\n|5|6|\n+-+-+\n"
1448
+ end
1449
+ end